UNPKG

consequunturatque

Version:

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

1,091 lines (1,061 loc) 68.9 kB
'use strict'; // --------------------------------------------------------------------------- const Exchange = require ('./base/Exchange'); const { AuthenticationError, ExchangeError, PermissionDenied, ExchangeNotAvailable, OnMaintenance, InvalidOrder, OrderNotFound, InsufficientFunds, ArgumentsRequired, BadSymbol, BadRequest, RequestTimeout, NetworkError } = require ('./base/errors'); const { TRUNCATE } = require ('./base/functions/number'); const Precise = require ('./base/Precise'); // --------------------------------------------------------------------------- module.exports = class huobipro extends Exchange { describe () { return this.deepExtend (super.describe (), { 'id': 'huobipro', 'name': 'Huobi Pro', 'countries': [ 'CN' ], 'rateLimit': 2000, 'userAgent': this.userAgents['chrome39'], 'version': 'v1', 'accounts': undefined, 'accountsById': undefined, 'hostname': 'api.huobi.pro', // api.testnet.huobi.pro 'pro': true, 'has': { 'cancelAllOrders': true, 'cancelOrder': true, 'cancelOrders': true, 'CORS': false, 'createOrder': true, 'fetchBalance': true, 'fetchClosedOrders': true, 'fetchCurrencies': true, 'fetchDepositAddress': true, 'fetchDeposits': true, 'fetchMarkets': true, 'fetchMyTrades': true, 'fetchOHLCV': true, 'fetchOpenOrders': true, 'fetchOrder': true, 'fetchOrderBook': true, 'fetchOrders': true, 'fetchTicker': true, 'fetchTickers': true, 'fetchTrades': true, 'fetchTradingLimits': true, 'fetchWithdrawals': true, 'withdraw': true, }, 'timeframes': { '1m': '1min', '5m': '5min', '15m': '15min', '30m': '30min', '1h': '60min', '4h': '4hour', '1d': '1day', '1w': '1week', '1M': '1mon', '1y': '1year', }, 'urls': { 'test': { 'market': 'https://api.testnet.huobi.pro', 'public': 'https://api.testnet.huobi.pro', 'private': 'https://api.testnet.huobi.pro', }, 'logo': 'https://user-images.githubusercontent.com/1294454/76137448-22748a80-604e-11ea-8069-6e389271911d.jpg', 'api': { 'market': 'https://{hostname}', 'public': 'https://{hostname}', 'private': 'https://{hostname}', 'v2Public': 'https://{hostname}', 'v2Private': 'https://{hostname}', }, 'www': 'https://www.huobi.com', 'referral': 'https://www.huobi.com/en-us/topic/invited/?invite_code=rwrd3', 'doc': 'https://huobiapi.github.io/docs/spot/v1/cn/', 'fees': 'https://www.huobi.com/about/fee/', }, 'api': { 'v2Public': { 'get': [ 'reference/currencies', ], }, 'v2Private': { 'get': [ 'account/ledger', 'account/withdraw/quota', 'account/withdraw/address', // 提币地址查询(限母用户可用) 'account/deposit/address', 'reference/transact-fee-rate', 'account/asset-valuation', // 获取账户资产估值 'point/account', // 点卡余额查询 'sub-user/user-list', // 获取子用户列表 'sub-user/user-state', // 获取特定子用户的用户状态 'sub-user/account-list', // 获取特定子用户的账户列表 'sub-user/deposit-address', // 子用户充币地址查询 'sub-user/query-deposit', // 子用户充币记录查询 'user/api-key', // 母子用户API key信息查询 ], 'post': [ 'account/transfer', 'point/transfer', // 点卡划转 'sub-user/management', // 冻结/解冻子用户 'sub-user/creation', // 子用户创建 'sub-user/tradable-market', // 设置子用户交易权限 'sub-user/transferability', // 设置子用户资产转出权限 'sub-user/api-key-generation', // 子用户API key创建 'sub-user/api-key-modification', // 修改子用户API key 'sub-user/api-key-deletion', // 删除子用户API key ], }, 'market': { 'get': [ 'history/kline', // 获取K线数据 'detail/merged', // 获取聚合行情(Ticker) 'depth', // 获取 Market Depth 数据 'trade', // 获取 Trade Detail 数据 'history/trade', // 批量获取最近的交易记录 'detail', // 获取 Market Detail 24小时成交量数据 'tickers', ], }, 'public': { 'get': [ 'common/symbols', // 查询系统支持的所有交易对 'common/currencys', // 查询系统支持的所有币种 'common/timestamp', // 查询系统当前时间 'common/exchange', // order limits 'settings/currencys', // ?language=en-US ], }, 'private': { 'get': [ 'account/accounts', // 查询当前用户的所有账户(即account-id) 'account/accounts/{id}/balance', // 查询指定账户的余额 'account/accounts/{sub-uid}', 'account/history', 'cross-margin/loan-info', 'margin/loan-info', // 查询借币币息率及额度 'fee/fee-rate/get', 'order/openOrders', 'order/orders', 'order/orders/{id}', // 查询某个订单详情 'order/orders/{id}/matchresults', // 查询某个订单的成交明细 'order/orders/getClientOrder', 'order/history', // 查询当前委托、历史委托 'order/matchresults', // 查询当前成交、历史成交 'dw/withdraw-virtual/addresses', // 查询虚拟币提现地址(Deprecated) 'query/deposit-withdraw', 'margin/loan-info', 'margin/loan-orders', // 借贷订单 'margin/accounts/balance', // 借贷账户详情 'cross-margin/loan-orders', // 查询借币订单 'cross-margin/accounts/balance', // 借币账户详情 'points/actions', 'points/orders', 'subuser/aggregate-balance', 'stable-coin/exchange_rate', 'stable-coin/quote', ], 'post': [ 'account/transfer', // 资产划转(该节点为母用户和子用户进行资产划转的通用接口。) 'futures/transfer', 'order/batch-orders', 'order/orders/place', // 创建并执行一个新订单 (一步下单, 推荐使用) 'order/orders/submitCancelClientOrder', 'order/orders/batchCancelOpenOrders', 'order/orders', // 创建一个新的订单请求 (仅创建订单,不执行下单) 'order/orders/{id}/place', // 执行一个订单 (仅执行已创建的订单) 'order/orders/{id}/submitcancel', // 申请撤销一个订单请求 'order/orders/batchcancel', // 批量撤销订单 'dw/balance/transfer', // 资产划转 'dw/withdraw/api/create', // 申请提现虚拟币 'dw/withdraw-virtual/create', // 申请提现虚拟币 'dw/withdraw-virtual/{id}/place', // 确认申请虚拟币提现(Deprecated) 'dw/withdraw-virtual/{id}/cancel', // 申请取消提现虚拟币 'dw/transfer-in/margin', // 现货账户划入至借贷账户 'dw/transfer-out/margin', // 借贷账户划出至现货账户 'margin/orders', // 申请借贷 'margin/orders/{id}/repay', // 归还借贷 'cross-margin/transfer-in', // 资产划转 'cross-margin/transfer-out', // 资产划转 'cross-margin/orders', // 申请借币 'cross-margin/orders/{id}/repay', // 归还借币 'stable-coin/exchange', 'subuser/transfer', ], }, }, 'fees': { 'trading': { 'feeSide': 'get', 'tierBased': false, 'percentage': true, 'maker': 0.002, 'taker': 0.002, }, }, 'exceptions': { 'broad': { 'contract is restricted of closing positions on API. Please contact customer service': OnMaintenance, 'maintain': OnMaintenance, }, 'exact': { // err-code 'bad-request': BadRequest, 'base-date-limit-error': BadRequest, // {"status":"error","err-code":"base-date-limit-error","err-msg":"date less than system limit","data":null} 'api-not-support-temp-addr': PermissionDenied, // {"status":"error","err-code":"api-not-support-temp-addr","err-msg":"API withdrawal does not support temporary addresses","data":null} 'timeout': RequestTimeout, // {"ts":1571653730865,"status":"error","err-code":"timeout","err-msg":"Request Timeout"} 'gateway-internal-error': ExchangeNotAvailable, // {"status":"error","err-code":"gateway-internal-error","err-msg":"Failed to load data. Try again later.","data":null} 'account-frozen-balance-insufficient-error': InsufficientFunds, // {"status":"error","err-code":"account-frozen-balance-insufficient-error","err-msg":"trade account balance is not enough, left: `0.0027`","data":null} 'invalid-amount': InvalidOrder, // eg "Paramemter `amount` is invalid." 'order-limitorder-amount-min-error': InvalidOrder, // limit order amount error, min: `0.001` 'order-limitorder-amount-max-error': InvalidOrder, // market order amount error, max: `1000000` 'order-marketorder-amount-min-error': InvalidOrder, // market order amount error, min: `0.01` 'order-limitorder-price-min-error': InvalidOrder, // limit order price error 'order-limitorder-price-max-error': InvalidOrder, // limit order price error 'order-holding-limit-failed': InvalidOrder, // {"status":"error","err-code":"order-holding-limit-failed","err-msg":"Order failed, exceeded the holding limit of this currency","data":null} 'order-orderprice-precision-error': InvalidOrder, // {"status":"error","err-code":"order-orderprice-precision-error","err-msg":"order price precision error, scale: `4`","data":null} 'order-orderstate-error': OrderNotFound, // canceling an already canceled order 'order-queryorder-invalid': OrderNotFound, // querying a non-existent order 'order-update-error': ExchangeNotAvailable, // undocumented error 'api-signature-check-failed': AuthenticationError, 'api-signature-not-valid': AuthenticationError, // {"status":"error","err-code":"api-signature-not-valid","err-msg":"Signature not valid: Incorrect Access key [Access key错误]","data":null} 'base-record-invalid': OrderNotFound, // https://github.com/ccxt/ccxt/issues/5750 'base-symbol-trade-disabled': BadSymbol, // {"status":"error","err-code":"base-symbol-trade-disabled","err-msg":"Trading is disabled for this symbol","data":null} 'base-symbol-error': BadSymbol, // {"status":"error","err-code":"base-symbol-error","err-msg":"The symbol is invalid","data":null} 'system-maintenance': OnMaintenance, // {"status": "error", "err-code": "system-maintenance", "err-msg": "System is in maintenance!", "data": null} // err-msg 'invalid symbol': BadSymbol, // {"ts":1568813334794,"status":"error","err-code":"invalid-parameter","err-msg":"invalid symbol"} 'symbol trade not open now': BadSymbol, // {"ts":1576210479343,"status":"error","err-code":"invalid-parameter","err-msg":"symbol trade not open now"} }, }, 'options': { // https://github.com/ccxt/ccxt/issues/5376 'fetchOrdersByStatesMethod': 'private_get_order_orders', // 'private_get_order_history' // https://github.com/ccxt/ccxt/pull/5392 'fetchOpenOrdersMethod': 'fetch_open_orders_v1', // 'fetch_open_orders_v2' // https://github.com/ccxt/ccxt/issues/5388 'createMarketBuyOrderRequiresPrice': true, 'fetchMarketsMethod': 'publicGetCommonSymbols', 'fetchBalanceMethod': 'privateGetAccountAccountsIdBalance', 'createOrderMethod': 'privatePostOrderOrdersPlace', 'language': 'en-US', }, 'commonCurrencies': { // https://github.com/ccxt/ccxt/issues/6081 // https://github.com/ccxt/ccxt/issues/3365 // https://github.com/ccxt/ccxt/issues/2873 'GET': 'Themis', // conflict with GET (Guaranteed Entrance Token, GET Protocol) 'HOT': 'Hydro Protocol', // conflict with HOT (Holo) https://github.com/ccxt/ccxt/issues/4929 // https://github.com/ccxt/ccxt/issues/7399 // https://coinmarketcap.com/currencies/pnetwork/ // https://coinmarketcap.com/currencies/penta/markets/ // https://en.cryptonomist.ch/blog/eidoo/the-edo-to-pnt-upgrade-what-you-need-to-know-updated/ 'PNT': 'Penta', 'SBTC': 'Super Bitcoin', 'BIFI': 'Bitcoin File', // conflict with Beefy.Finance https://github.com/ccxt/ccxt/issues/8706 }, }); } async fetchTradingLimits (symbols = undefined, params = {}) { // this method should not be called directly, use loadTradingLimits () instead // by default it will try load withdrawal fees of all currencies (with separate requests) // however if you define symbols = [ 'ETH/BTC', 'LTC/BTC' ] in args it will only load those await this.loadMarkets (); if (symbols === undefined) { symbols = this.symbols; } const result = {}; for (let i = 0; i < symbols.length; i++) { const symbol = symbols[i]; result[symbol] = await this.fetchTradingLimitsById (this.marketId (symbol), params); } return result; } async fetchTradingLimitsById (id, params = {}) { const request = { 'symbol': id, }; const response = await this.publicGetCommonExchange (this.extend (request, params)); // // { status: "ok", // data: { symbol: "aidocbtc", // 'buy-limit-must-less-than': 1.1, // 'sell-limit-must-greater-than': 0.9, // 'limit-order-must-greater-than': 1, // 'limit-order-must-less-than': 5000000, // 'market-buy-order-must-greater-than': 0.0001, // 'market-buy-order-must-less-than': 100, // 'market-sell-order-must-greater-than': 1, // 'market-sell-order-must-less-than': 500000, // 'circuit-break-when-greater-than': 10000, // 'circuit-break-when-less-than': 10, // 'market-sell-order-rate-must-less-than': 0.1, // 'market-buy-order-rate-must-less-than': 0.1 } } // return this.parseTradingLimits (this.safeValue (response, 'data', {})); } parseTradingLimits (limits, symbol = undefined, params = {}) { // // { symbol: "aidocbtc", // 'buy-limit-must-less-than': 1.1, // 'sell-limit-must-greater-than': 0.9, // 'limit-order-must-greater-than': 1, // 'limit-order-must-less-than': 5000000, // 'market-buy-order-must-greater-than': 0.0001, // 'market-buy-order-must-less-than': 100, // 'market-sell-order-must-greater-than': 1, // 'market-sell-order-must-less-than': 500000, // 'circuit-break-when-greater-than': 10000, // 'circuit-break-when-less-than': 10, // 'market-sell-order-rate-must-less-than': 0.1, // 'market-buy-order-rate-must-less-than': 0.1 } // return { 'info': limits, 'limits': { 'amount': { 'min': this.safeNumber (limits, 'limit-order-must-greater-than'), 'max': this.safeNumber (limits, 'limit-order-must-less-than'), }, }, }; } costToPrecision (symbol, cost) { return this.decimalToPrecision (cost, TRUNCATE, this.markets[symbol]['precision']['cost'], this.precisionMode); } async fetchMarkets (params = {}) { const method = this.options['fetchMarketsMethod']; const response = await this[method] (params); const markets = this.safeValue (response, 'data'); const numMarkets = markets.length; if (numMarkets < 1) { throw new NetworkError (this.id + ' publicGetCommonSymbols returned empty response: ' + this.json (markets)); } const result = []; for (let i = 0; i < markets.length; i++) { const market = markets[i]; const baseId = this.safeString (market, 'base-currency'); const quoteId = this.safeString (market, 'quote-currency'); const id = baseId + quoteId; const base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); const symbol = base + '/' + quote; const precision = { 'amount': this.safeInteger (market, 'amount-precision'), 'price': this.safeInteger (market, 'price-precision'), 'cost': this.safeInteger (market, 'value-precision'), }; const maker = (base === 'OMG') ? 0 : 0.2 / 100; const taker = (base === 'OMG') ? 0 : 0.2 / 100; const minAmount = this.safeNumber (market, 'min-order-amt', Math.pow (10, -precision['amount'])); const maxAmount = this.safeNumber (market, 'max-order-amt'); const minCost = this.safeNumber (market, 'min-order-value', 0); const state = this.safeString (market, 'state'); const active = (state === 'online'); result.push ({ 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'baseId': baseId, 'quoteId': quoteId, 'active': active, 'precision': precision, 'taker': taker, 'maker': maker, 'limits': { 'amount': { 'min': minAmount, 'max': maxAmount, }, 'price': { 'min': Math.pow (10, -precision['price']), 'max': undefined, }, 'cost': { 'min': minCost, 'max': undefined, }, }, 'info': market, }); } return result; } parseTicker (ticker, market = undefined) { // // fetchTicker // // { // "amount": 26228.672978342216, // "open": 9078.95, // "close": 9146.86, // "high": 9155.41, // "id": 209988544334, // "count": 265846, // "low": 8988.0, // "version": 209988544334, // "ask": [ 9146.87, 0.156134 ], // "vol": 2.3822168242201668E8, // "bid": [ 9146.86, 0.080758 ], // } // // fetchTickers // { // symbol: "bhdht", // open: 2.3938, // high: 2.4151, // low: 2.3323, // close: 2.3909, // amount: 628.992, // vol: 1493.71841095, // count: 2088, // bid: 2.3643, // bidSize: 0.7136, // ask: 2.4061, // askSize: 0.4156 // } // let symbol = undefined; if (market !== undefined) { symbol = market['symbol']; } const timestamp = this.safeInteger (ticker, 'ts'); let bid = undefined; let bidVolume = undefined; let ask = undefined; let askVolume = undefined; if ('bid' in ticker) { if (Array.isArray (ticker['bid'])) { bid = this.safeNumber (ticker['bid'], 0); bidVolume = this.safeNumber (ticker['bid'], 1); } else { bid = this.safeNumber (ticker, 'bid'); bidVolume = this.safeValue (ticker, 'bidSize'); } } if ('ask' in ticker) { if (Array.isArray (ticker['ask'])) { ask = this.safeNumber (ticker['ask'], 0); askVolume = this.safeNumber (ticker['ask'], 1); } else { ask = this.safeNumber (ticker, 'ask'); askVolume = this.safeValue (ticker, 'askSize'); } } const open = this.safeNumber (ticker, 'open'); const close = this.safeNumber (ticker, 'close'); let change = undefined; let percentage = undefined; let average = undefined; if ((open !== undefined) && (close !== undefined)) { change = close - open; average = this.sum (open, close) / 2; if ((close !== undefined) && (close > 0)) { percentage = (change / open) * 100; } } const baseVolume = this.safeNumber (ticker, 'amount'); const quoteVolume = this.safeNumber (ticker, 'vol'); const vwap = this.vwap (baseVolume, quoteVolume); return { 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'high': this.safeNumber (ticker, 'high'), 'low': this.safeNumber (ticker, 'low'), 'bid': bid, 'bidVolume': bidVolume, 'ask': ask, 'askVolume': askVolume, 'vwap': vwap, 'open': open, 'close': close, 'last': close, 'previousClose': undefined, 'change': change, 'percentage': percentage, 'average': average, 'baseVolume': baseVolume, 'quoteVolume': quoteVolume, 'info': ticker, }; } async fetchOrderBook (symbol, limit = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], 'type': 'step0', }; const response = await this.marketGetDepth (this.extend (request, params)); // // { // "status": "ok", // "ch": "market.btcusdt.depth.step0", // "ts": 1583474832790, // "tick": { // "bids": [ // [ 9100.290000000000000000, 0.200000000000000000 ], // [ 9099.820000000000000000, 0.200000000000000000 ], // [ 9099.610000000000000000, 0.205000000000000000 ], // ], // "asks": [ // [ 9100.640000000000000000, 0.005904000000000000 ], // [ 9101.010000000000000000, 0.287311000000000000 ], // [ 9101.030000000000000000, 0.012121000000000000 ], // ], // "ts":1583474832008, // "version":104999698780 // } // } // if ('tick' in response) { if (!response['tick']) { throw new BadSymbol (this.id + ' fetchOrderBook() returned empty response: ' + this.json (response)); } const tick = this.safeValue (response, 'tick'); const timestamp = this.safeInteger (tick, 'ts', this.safeInteger (response, 'ts')); const result = this.parseOrderBook (tick, symbol, timestamp); result['nonce'] = this.safeInteger (tick, 'version'); return result; } throw new ExchangeError (this.id + ' fetchOrderBook() returned unrecognized response: ' + this.json (response)); } async fetchTicker (symbol, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], }; const response = await this.marketGetDetailMerged (this.extend (request, params)); // // { // "status": "ok", // "ch": "market.btcusdt.detail.merged", // "ts": 1583494336669, // "tick": { // "amount": 26228.672978342216, // "open": 9078.95, // "close": 9146.86, // "high": 9155.41, // "id": 209988544334, // "count": 265846, // "low": 8988.0, // "version": 209988544334, // "ask": [ 9146.87, 0.156134 ], // "vol": 2.3822168242201668E8, // "bid": [ 9146.86, 0.080758 ], // } // } // const ticker = this.parseTicker (response['tick'], market); const timestamp = this.safeInteger (response, 'ts'); ticker['timestamp'] = timestamp; ticker['datetime'] = this.iso8601 (timestamp); return ticker; } async fetchTickers (symbols = undefined, params = {}) { await this.loadMarkets (); const response = await this.marketGetTickers (params); const tickers = this.safeValue (response, 'data'); const timestamp = this.safeInteger (response, 'ts'); const result = {}; for (let i = 0; i < tickers.length; i++) { const marketId = this.safeString (tickers[i], 'symbol'); const market = this.safeMarket (marketId); const symbol = market['symbol']; const ticker = this.parseTicker (tickers[i], market); ticker['timestamp'] = timestamp; ticker['datetime'] = this.iso8601 (timestamp); result[symbol] = ticker; } return this.filterByArray (result, 'symbol', symbols); } parseTrade (trade, market = undefined) { // // fetchTrades (public) // // { // "amount": 0.010411000000000000, // "trade-id": 102090736910, // "ts": 1583497692182, // "id": 10500517034273194594947, // "price": 9096.050000000000000000, // "direction": "sell" // } // // fetchMyTrades (private) // // { // 'symbol': 'swftcbtc', // 'fee-currency': 'swftc', // 'filled-fees': '0', // 'source': 'spot-api', // 'id': 83789509854000, // 'type': 'buy-limit', // 'order-id': 83711103204909, // 'filled-points': '0.005826843283532154', // 'fee-deduct-currency': 'ht', // 'filled-amount': '45941.53', // 'price': '0.0000001401', // 'created-at': 1597933260729, // 'match-id': 100087455560, // 'role': 'maker', // 'trade-id': 100050305348 // }, // const marketId = this.safeString (trade, 'symbol'); const symbol = this.safeSymbol (marketId, market); const timestamp = this.safeInteger2 (trade, 'ts', 'created-at'); const order = this.safeString (trade, 'order-id'); let side = this.safeString (trade, 'direction'); let type = this.safeString (trade, 'type'); if (type !== undefined) { const typeParts = type.split ('-'); side = typeParts[0]; type = typeParts[1]; } const takerOrMaker = this.safeString (trade, 'role'); const priceString = this.safeString (trade, 'price'); const amountString = this.safeString2 (trade, 'filled-amount', 'amount'); const price = this.parseNumber (priceString); const amount = this.parseNumber (amountString); const cost = this.parseNumber (Precise.stringMul (priceString, amountString)); let fee = undefined; let feeCost = this.safeNumber (trade, 'filled-fees'); let feeCurrency = this.safeCurrencyCode (this.safeString (trade, 'fee-currency')); const filledPoints = this.safeNumber (trade, 'filled-points'); if (filledPoints !== undefined) { if ((feeCost === undefined) || (feeCost === 0.0)) { feeCost = filledPoints; feeCurrency = this.safeCurrencyCode (this.safeString (trade, 'fee-deduct-currency')); } } if (feeCost !== undefined) { fee = { 'cost': feeCost, 'currency': feeCurrency, }; } const tradeId = this.safeString2 (trade, 'trade-id', 'tradeId'); const id = this.safeString (trade, 'id', tradeId); return { 'id': id, 'info': trade, 'order': order, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'symbol': symbol, 'type': type, 'side': side, 'takerOrMaker': takerOrMaker, 'price': price, 'amount': amount, 'cost': cost, 'fee': fee, }; } async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); let market = undefined; const request = {}; if (symbol !== undefined) { market = this.market (symbol); request['symbol'] = market['id']; } if (limit !== undefined) { request['size'] = limit; // 1-100 orders, default is 100 } if (since !== undefined) { request['start-date'] = this.ymd (since); // a date within 61 days from today request['end-date'] = this.ymd (this.sum (since, 86400000)); } const response = await this.privateGetOrderMatchresults (this.extend (request, params)); const trades = this.parseTrades (response['data'], market, since, limit); return trades; } async fetchTrades (symbol, since = undefined, limit = 1000, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], }; if (limit !== undefined) { request['size'] = limit; } const response = await this.marketGetHistoryTrade (this.extend (request, params)); // // { // "status": "ok", // "ch": "market.btcusdt.trade.detail", // "ts": 1583497692365, // "data": [ // { // "id": 105005170342, // "ts": 1583497692182, // "data": [ // { // "amount": 0.010411000000000000, // "trade-id": 102090736910, // "ts": 1583497692182, // "id": 10500517034273194594947, // "price": 9096.050000000000000000, // "direction": "sell" // } // ] // }, // // ... // ] // } // const data = this.safeValue (response, 'data'); let result = []; for (let i = 0; i < data.length; i++) { const trades = this.safeValue (data[i], 'data', []); for (let j = 0; j < trades.length; j++) { const trade = this.parseTrade (trades[j], market); result.push (trade); } } result = this.sortBy (result, 'timestamp'); return this.filterBySymbolSinceLimit (result, symbol, since, limit); } parseOHLCV (ohlcv, market = undefined) { // // { // "amount":1.2082, // "open":0.025096, // "close":0.025095, // "high":0.025096, // "id":1591515300, // "count":6, // "low":0.025095, // "vol":0.0303205097 // } // return [ this.safeTimestamp (ohlcv, 'id'), this.safeNumber (ohlcv, 'open'), this.safeNumber (ohlcv, 'high'), this.safeNumber (ohlcv, 'low'), this.safeNumber (ohlcv, 'close'), this.safeNumber (ohlcv, 'amount'), ]; } async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = 1000, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], 'period': this.timeframes[timeframe], }; if (limit !== undefined) { request['size'] = limit; } const response = await this.marketGetHistoryKline (this.extend (request, params)); // // { // "status":"ok", // "ch":"market.ethbtc.kline.1min", // "ts":1591515374371, // "data":[ // {"amount":0.0,"open":0.025095,"close":0.025095,"high":0.025095,"id":1591515360,"count":0,"low":0.025095,"vol":0.0}, // {"amount":1.2082,"open":0.025096,"close":0.025095,"high":0.025096,"id":1591515300,"count":6,"low":0.025095,"vol":0.0303205097}, // {"amount":0.0648,"open":0.025096,"close":0.025096,"high":0.025096,"id":1591515240,"count":2,"low":0.025096,"vol":0.0016262208}, // ] // } // const data = this.safeValue (response, 'data', []); return this.parseOHLCVs (data, market, timeframe, since, limit); } async fetchAccounts (params = {}) { await this.loadMarkets (); const response = await this.privateGetAccountAccounts (params); return response['data']; } async fetchCurrencies (params = {}) { const request = { 'language': this.options['language'], }; const response = await this.publicGetSettingsCurrencys (this.extend (request, params)); const currencies = this.safeValue (response, 'data'); const result = {}; for (let i = 0; i < currencies.length; i++) { const currency = currencies[i]; // // { name: "ctxc", // 'display-name': "CTXC", // 'withdraw-precision': 8, // 'currency-type': "eth", // 'currency-partition': "pro", // 'support-sites': null, // 'otc-enable': 0, // 'deposit-min-amount': "2", // 'withdraw-min-amount': "4", // 'show-precision': "8", // weight: "2988", // visible: true, // 'deposit-desc': "Please don’t deposit any other digital assets except CTXC t…", // 'withdraw-desc': "Minimum withdrawal amount: 4 CTXC. !>_<!For security reason…", // 'deposit-enabled': true, // 'withdraw-enabled': true, // 'currency-addr-with-tag': false, // 'fast-confirms': 15, // 'safe-confirms': 30 } // const id = this.safeValue (currency, 'name'); const precision = this.safeInteger (currency, 'withdraw-precision'); const code = this.safeCurrencyCode (id); const active = currency['visible'] && currency['deposit-enabled'] && currency['withdraw-enabled']; const name = this.safeString (currency, 'display-name'); result[code] = { 'id': id, 'code': code, 'type': 'crypto', // 'payin': currency['deposit-enabled'], // 'payout': currency['withdraw-enabled'], // 'transfer': undefined, 'name': name, 'active': active, 'fee': undefined, // todo need to fetch from fee endpoint 'precision': precision, 'limits': { 'amount': { 'min': Math.pow (10, -precision), 'max': Math.pow (10, precision), }, 'deposit': { 'min': this.safeNumber (currency, 'deposit-min-amount'), 'max': Math.pow (10, precision), }, 'withdraw': { 'min': this.safeNumber (currency, 'withdraw-min-amount'), 'max': Math.pow (10, precision), }, }, 'info': currency, }; } return result; } async fetchBalance (params = {}) { await this.loadMarkets (); await this.loadAccounts (); const method = this.options['fetchBalanceMethod']; const request = { 'id': this.accounts[0]['id'], }; const response = await this[method] (this.extend (request, params)); const balances = this.safeValue (response['data'], 'list', []); const result = { 'info': response }; for (let i = 0; i < balances.length; i++) { const balance = balances[i]; const currencyId = this.safeString (balance, 'currency'); const code = this.safeCurrencyCode (currencyId); let account = undefined; if (code in result) { account = result[code]; } else { account = this.account (); } if (balance['type'] === 'trade') { account['free'] = this.safeString (balance, 'balance'); } if (balance['type'] === 'frozen') { account['used'] = this.safeString (balance, 'balance'); } result[code] = account; } return this.parseBalance (result, false); } async fetchOrdersByStates (states, symbol = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const request = { 'states': states, }; let market = undefined; if (symbol !== undefined) { market = this.market (symbol); request['symbol'] = market['id']; } const method = this.safeString (this.options, 'fetchOrdersByStatesMethod', 'private_get_order_orders'); const response = await this[method] (this.extend (request, params)); // // { status: "ok", // data: [ { id: 13997833014, // symbol: "ethbtc", // 'account-id': 3398321, // amount: "0.045000000000000000", // price: "0.034014000000000000", // 'created-at': 1545836976871, // type: "sell-limit", // 'field-amount': "0.045000000000000000", // 'field-cash-amount': "0.001530630000000000", // 'field-fees': "0.000003061260000000", // 'finished-at': 1545837948214, // source: "spot-api", // state: "filled", // 'canceled-at': 0 } ] } // return this.parseOrders (response['data'], market, since, limit); } async fetchOrder (id, symbol = undefined, params = {}) { await this.loadMarkets (); const request = { 'id': id, }; const response = await this.privateGetOrderOrdersId (this.extend (request, params)); const order = this.safeValue (response, 'data'); return this.parseOrder (order); } async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { return await this.fetchOrdersByStates ('pre-submitted,submitted,partial-filled,filled,partial-canceled,canceled', symbol, since, limit, params); } async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { const method = this.safeString (this.options, 'fetchOpenOrdersMethod', 'fetch_open_orders_v1'); return await this[method] (symbol, since, limit, params); } async fetchOpenOrdersV1 (symbol = undefined, since = undefined, limit = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' fetchOpenOrdersV1() requires a symbol argument'); } return await this.fetchOrdersByStates ('pre-submitted,submitted,partial-filled', symbol, since, limit, params); } async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { return await this.fetchOrdersByStates ('filled,partial-canceled,canceled', symbol, since, limit, params); } async fetchOpenOrdersV2 (symbol = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' fetchOpenOrders() requires a symbol argument'); } const market = this.market (symbol); let accountId = this.safeString (params, 'account-id'); if (accountId === undefined) { // pick the first account await this.loadAccounts (); for (let i = 0; i < this.accounts.length; i++) { const account = this.accounts[i]; if (account['type'] === 'spot') { accountId = this.safeString (account, 'id'); if (accountId !== undefined) { break; } } } } const request = { 'symbol': market['id'], 'account-id': accountId, }; if (limit !== undefined) { request['size'] = limit; } const omitted = this.omit (params, 'account-id'); const response = await this.privateGetOrderOpenOrders (this.extend (request, omitted)); // // { // "status":"ok", // "data":[ // { // "symbol":"ethusdt", // "source":"api", // "amount":"0.010000000000000000", // "account-id":1528640, // "created-at":1561597491963, // "price":"400.000000000000000000", // "filled-amount":"0.0", // "filled-cash-amount":"0.0", // "filled-fees":"0.0", // "id":38477101630, // "state":"submitted", // "type":"sell-limit" // } // ] // } // const data = this.safeValue (response, 'data', []); return this.parseOrders (data, market, since, limit); } parseOrderStatus (status) { const statuses = { 'partial-filled': 'open', 'partial-canceled': 'canceled', 'filled': 'closed', 'canceled': 'canceled', 'submitted': 'open', }; return this.safeString (statuses, status, status); } parseOrder (order, market = undefined) { // // { id: 13997833014, // symbol: "ethbtc", // 'account-id': 3398321, // amount: "0.045000000000000000", // price: "0.034014000000000000", // 'created-at': 1545836976871, // type: "sell-limit", // 'field-amount': "0.045000000000000000", // they have fixed it for filled-amount // 'field-cash-amount': "0.001530630000000000", // they have fixed it for filled-cash-amount // 'field-fees': "0.000003061260000000", // they have fixed it for filled-fees // 'finished-at': 1545837948214, // source: "spot-api", // state: "filled", // 'canceled-at': 0 } // // { id: 20395337822, // symbol: "ethbtc", // 'account-id': 5685075, // amount: "0.001000000000000000", // price: "0.0", // 'created-at': 1545831584023, // type: "buy-market", // 'field-amount': "0.029100000000000000", // they have fixed it for filled-amount // 'field-cash-amount': "0.000999788700000000", // they have fixed it for filled-cash-amount // 'field-fees': "0.000058200000000000", // they have fixed it for filled-fees // 'finished-at': 1545831584181, // source: "spot-api", // state: "filled", // 'canceled-at': 0 } // const id = this.safeString (order, 'id'); let side = undefined; let type = undefined; let status = undefined; if ('type' in order) { const orderType = order['type'].split ('-'); side = orderType[0]; type = orderType[1]; status = this.parseOrderStatus (this.safeString (order, 'state')); } const marketId = this.safeString (order, 'symbol'); const symbol = this.safeSymbol (marketId, market); const timestamp = this.safeInteger (order, 'created-at'); const amount = this.safeNumber (order, 'amount'); const filled = this.safeNumber2 (order, 'filled-amount', 'field-amount'); // typo in their API, filled amount let price = this.safeNumber (order, 'price'); if (price === 0.0) { price = undefined; } const cost = this.safeNumber2 (order, 'filled-cash-amount', 'field-cash-amount'); // same typo const feeCost = this.safeNumber2 (order, 'filled-fees', 'field-fees'); // typo in their API, filled fees let fee = undefined; if (feeCost !== undefined) { let feeCurrency = undefined; if (market !== undefined) { feeCurrency = (side === 'sell') ? market['quote'] : market['base']; } fee = { 'cost': feeCost, 'currency': feeCurrency, }; } return this.safeOrder ({ 'info': order, 'id': id, 'clientOrderId': undefined, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'lastTr