ccxt-look
Version:
1,065 lines (1,049 loc) • 110 kB
JavaScript
'use strict';
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { AuthenticationError, ExchangeNotAvailable, AccountSuspended, PermissionDenied, RateLimitExceeded, InvalidNonce, InvalidAddress, ArgumentsRequired, ExchangeError, InvalidOrder, InsufficientFunds, BadRequest, OrderNotFound, BadSymbol, NotSupported } = require ('./base/errors');
const { TICK_SIZE, TRUNCATE } = require ('./base/functions/number');
const Precise = require ('./base/Precise');
// ---------------------------------------------------------------------------
module.exports = class bitmart extends Exchange {
describe () {
return this.deepExtend (super.describe (), {
'id': 'bitmart',
'name': 'BitMart',
'countries': [ 'US', 'CN', 'HK', 'KR' ],
'rateLimit': 250, // a bit slower than 50 times per second ~40 times per second
'version': 'v1',
'certified': true,
'pro': true,
'has': {
'CORS': undefined,
'spot': true,
'margin': undefined, // has but unimplemented
'swap': undefined, // has but unimplemented
'future': undefined, // has but unimplemented
'option': undefined,
'cancelAllOrders': true,
'cancelOrder': true,
'cancelOrders': true,
'createOrder': true,
'fetchBalance': true,
'fetchCanceledOrders': true,
'fetchClosedOrders': true,
'fetchCurrencies': true,
'fetchDeposit': true,
'fetchDepositAddress': true,
'fetchDepositAddresses': false,
'fetchDepositAddressesByNetwork': false,
'fetchDeposits': true,
'fetchFundingFee': true,
'fetchFundingFees': false,
'fetchFundingHistory': undefined,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchOrderTrades': true,
'fetchStatus': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTime': true,
'fetchTrades': true,
'fetchTradingFee': false,
'fetchTradingFees': false,
'fetchTransfer': false,
'fetchTransfers': false,
'fetchWithdrawAddressesByNetwork': false,
'fetchWithdrawal': true,
'fetchWithdrawals': true,
'reduceMargin': false,
'setLeverage': false,
'setMarginMode': false,
'transfer': false,
'withdraw': true,
},
'hostname': 'bitmart.com', // bitmart.info, bitmart.news for Hong Kong users
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/129991357-8f47464b-d0f4-41d6-8a82-34122f0d1398.jpg',
'api': {
'rest': 'https://api-cloud.{hostname}', // bitmart.info for Hong Kong users
},
'www': 'https://www.bitmart.com/',
'doc': 'https://developer-pro.bitmart.com/',
'referral': {
'url': 'http://www.bitmart.com/?r=rQCFLh',
'discount': 0.3,
},
'fees': 'https://www.bitmart.com/fee/en',
},
'requiredCredentials': {
'apiKey': true,
'secret': true,
'uid': true,
},
'api': {
'public': {
'system': {
'get': {
'time': 5, // https://api-cloud.bitmart.com/system/time
'service': 5, // https://api-cloud.bitmart.com/system/service
},
},
'account': {
'get': {
'currencies': 10, // https://api-cloud.bitmart.com/account/v1/currencies
},
},
'spot': {
'get': {
'currencies': 1,
'symbols': 1,
'symbols/details': 1,
'ticker': 1, // ?symbol=BTC_USDT
'steps': 1, // ?symbol=BMX_ETH
'symbols/kline': 1, // ?symbol=BMX_ETH&step=15&from=1525760116&to=1525769116
'symbols/book': 1, // ?symbol=BMX_ETH&precision=6
'symbols/trades': 1, // ?symbol=BMX_ETH
},
},
'contract': {
'get': {
'tickers': 0.5,
},
},
},
'private': {
'account': {
'get': {
'wallet': 0.5, // ?account_type=1
'deposit/address': 1, // ?currency=USDT-TRC20
'withdraw/charge': 1, // ?currency=BTC
'deposit-withdraw/history': 1, // ?limit=10&offset=1&operationType=withdraw
'deposit-withdraw/detail': 1, // ?id=1679952
},
'post': {
'withdraw/apply': 1,
},
},
'spot': {
'get': {
'wallet': 0.5,
'order_detail': 0.1,
'orders': 0.5,
'trades': 0.5,
},
'post': {
'submit_order': 0.1, // https://api-cloud.bitmart.com/spot/v1/submit_order
'cancel_order': 0.1, // https://api-cloud.bitmart.com/spot/v2/cancel_order
'cancel_orders': 0.1,
},
},
},
},
'timeframes': {
'1m': 1,
'3m': 3,
'5m': 5,
'15m': 15,
'30m': 30,
'45m': 45,
'1h': 60,
'2h': 120,
'3h': 180,
'4h': 240,
'1d': 1440,
'1w': 10080,
'1M': 43200,
},
'fees': {
'trading': {
'tierBased': true,
'percentage': true,
'taker': this.parseNumber ('0.0025'),
'maker': this.parseNumber ('0.0025'),
'tiers': {
'taker': [
[ this.parseNumber ('0'), this.parseNumber ('0.0020') ],
[ this.parseNumber ('10'), this.parseNumber ('0.18') ],
[ this.parseNumber ('50'), this.parseNumber ('0.0016') ],
[ this.parseNumber ('250'), this.parseNumber ('0.0014') ],
[ this.parseNumber ('1000'), this.parseNumber ('0.0012') ],
[ this.parseNumber ('5000'), this.parseNumber ('0.0010') ],
[ this.parseNumber ('25000'), this.parseNumber ('0.0008') ],
[ this.parseNumber ('50000'), this.parseNumber ('0.0006') ],
],
'maker': [
[ this.parseNumber ('0'), this.parseNumber ('0.001') ],
[ this.parseNumber ('10'), this.parseNumber ('0.0009') ],
[ this.parseNumber ('50'), this.parseNumber ('0.0008') ],
[ this.parseNumber ('250'), this.parseNumber ('0.0007') ],
[ this.parseNumber ('1000'), this.parseNumber ('0.0006') ],
[ this.parseNumber ('5000'), this.parseNumber ('0.0005') ],
[ this.parseNumber ('25000'), this.parseNumber ('0.0004') ],
[ this.parseNumber ('50000'), this.parseNumber ('0.0003') ],
],
},
},
},
'precisionMode': TICK_SIZE,
'exceptions': {
'exact': {
// general errors
'30000': ExchangeError, // 404, Not found
'30001': AuthenticationError, // 401, Header X-BM-KEY is empty
'30002': AuthenticationError, // 401, Header X-BM-KEY not found
'30003': AccountSuspended, // 401, Header X-BM-KEY has frozen
'30004': AuthenticationError, // 401, Header X-BM-SIGN is empty
'30005': AuthenticationError, // 401, Header X-BM-SIGN is wrong
'30006': AuthenticationError, // 401, Header X-BM-TIMESTAMP is empty
'30007': AuthenticationError, // 401, Header X-BM-TIMESTAMP range. Within a minute
'30008': AuthenticationError, // 401, Header X-BM-TIMESTAMP invalid format
'30010': PermissionDenied, // 403, IP is forbidden. We recommend enabling IP whitelist for API trading. After that reauth your account
'30011': AuthenticationError, // 403, Header X-BM-KEY over expire time
'30012': AuthenticationError, // 403, Header X-BM-KEY is forbidden to request it
'30013': RateLimitExceeded, // 429, Request too many requests
'30014': ExchangeNotAvailable, // 503, Service unavailable
// funding account errors
'60000': BadRequest, // 400, Invalid request (maybe the body is empty, or the int parameter passes string data)
'60001': BadRequest, // 400, Asset account type does not exist
'60002': BadRequest, // 400, currency does not exist
'60003': ExchangeError, // 400, Currency has been closed recharge channel, if there is any problem, please consult customer service
'60004': ExchangeError, // 400, Currency has been closed withdraw channel, if there is any problem, please consult customer service
'60005': ExchangeError, // 400, Minimum amount is %s
'60006': ExchangeError, // 400, Maximum withdraw precision is %d
'60007': InvalidAddress, // 400, Only withdrawals from added addresses are allowed
'60008': InsufficientFunds, // 400, Balance not enough
'60009': ExchangeError, // 400, Beyond the limit
'60010': ExchangeError, // 400, Withdraw id or deposit id not found
'60011': InvalidAddress, // 400, Address is not valid
'60012': ExchangeError, // 400, This action is not supported in this currency(If IOTA, HLX recharge and withdraw calls are prohibited)
'60020': PermissionDenied, // 403, Your account is not allowed to recharge
'60021': PermissionDenied, // 403, Your account is not allowed to withdraw
'60022': PermissionDenied, // 403, No withdrawals for 24 hours
'60030': BadRequest, // 405, Method Not Allowed
'60031': BadRequest, // 415, Unsupported Media Type
'60050': ExchangeError, // 500, User account not found
'60051': ExchangeError, // 500, Internal Server Error
// spot errors
'50000': BadRequest, // 400, Bad Request
'50001': BadSymbol, // 400, Symbol not found
'50002': BadRequest, // 400, From Or To format error
'50003': BadRequest, // 400, Step format error
'50004': BadRequest, // 400, Kline size over 500
'50005': OrderNotFound, // 400, Order Id not found
'50006': InvalidOrder, // 400, Minimum size is %s
'50007': InvalidOrder, // 400, Maximum size is %s
'50008': InvalidOrder, // 400, Minimum price is %s
'50009': InvalidOrder, // 400, Minimum count*price is %s
'50010': InvalidOrder, // 400, RequestParam size is required
'50011': InvalidOrder, // 400, RequestParam price is required
'50012': InvalidOrder, // 400, RequestParam notional is required
'50013': InvalidOrder, // 400, Maximum limit*offset is %d
'50014': BadRequest, // 400, RequestParam limit is required
'50015': BadRequest, // 400, Minimum limit is 1
'50016': BadRequest, // 400, Maximum limit is %d
'50017': BadRequest, // 400, RequestParam offset is required
'50018': BadRequest, // 400, Minimum offset is 1
'50019': BadRequest, // 400, Maximum price is %s
// '50019': ExchangeError, // 400, Invalid status. validate status is [1=Failed, 2=Success, 3=Frozen Failed, 4=Frozen Success, 5=Partially Filled, 6=Fully Fulled, 7=Canceling, 8=Canceled
'50020': InsufficientFunds, // 400, Balance not enough
'50021': BadRequest, // 400, Invalid %s
'50022': ExchangeNotAvailable, // 400, Service unavailable
'50023': BadSymbol, // 400, This Symbol can't place order by api
'50029': InvalidOrder, // {"message":"param not match : size * price >=1000","code":50029,"trace":"f931f030-b692-401b-a0c5-65edbeadc598","data":{}}
'50030': InvalidOrder, // {"message":"Order is already canceled","code":50030,"trace":"8d6f64ee-ad26-45a4-9efd-1080f9fca1fa","data":{}}
// below Error codes used interchangeably for both failed postOnly and IOC orders depending on market price and order side
'50035': InvalidOrder, // {"message":"The price is low and there is no matching depth","code":50035,"trace":"677f01c7-8b88-4346-b097-b4226c75c90e","data":{}}
'50034': InvalidOrder, // {"message":"The price is high and there is no matching depth","code":50034,"trace":"ebfae59a-ba69-4735-86b2-0ed7b9ca14ea","data":{}}
'53000': AccountSuspended, // 403, Your account is frozen due to security policies. Please contact customer service
'53001': AccountSuspended, // {"message":"Your kyc country is restricted. Please contact customer service.","code":53001,"trace":"8b445940-c123-4de9-86d7-73c5be2e7a24","data":{}}
'57001': BadRequest, // 405, Method Not Allowed
'58001': BadRequest, // 415, Unsupported Media Type
'59001': ExchangeError, // 500, User account not found
'59002': ExchangeError, // 500, Internal Server Error
// contract errors
'40001': ExchangeError, // 400, Cloud account not found
'40002': ExchangeError, // 400, out_trade_no not found
'40003': ExchangeError, // 400, out_trade_no already existed
'40004': ExchangeError, // 400, Cloud account count limit
'40005': ExchangeError, // 400, Transfer vol precision error
'40006': PermissionDenied, // 400, Invalid ip error
'40007': BadRequest, // 400, Parse parameter error
'40008': InvalidNonce, // 400, Check nonce error
'40009': BadRequest, // 400, Check ver error
'40010': BadRequest, // 400, Not found func error
'40011': BadRequest, // 400, Invalid request
'40012': ExchangeError, // 500, System error
'40013': ExchangeError, // 400, Access too often" CLIENT_TIME_INVALID, "Please check your system time.
'40014': BadSymbol, // 400, This contract is offline
'40015': BadSymbol, // 400, This contract's exchange has been paused
'40016': InvalidOrder, // 400, This order would trigger user position liquidate
'40017': InvalidOrder, // 400, It is not possible to open and close simultaneously in the same position
'40018': InvalidOrder, // 400, Your position is closed
'40019': ExchangeError, // 400, Your position is in liquidation delegating
'40020': InvalidOrder, // 400, Your position volume is not enough
'40021': ExchangeError, // 400, The position is not exsit
'40022': ExchangeError, // 400, The position is not isolated
'40023': ExchangeError, // 400, The position would liquidate when sub margin
'40024': ExchangeError, // 400, The position would be warnning of liquidation when sub margin
'40025': ExchangeError, // 400, The position’s margin shouldn’t be lower than the base limit
'40026': ExchangeError, // 400, You cross margin position is in liquidation delegating
'40027': InsufficientFunds, // 400, You contract account available balance not enough
'40028': PermissionDenied, // 400, Your plan order's count is more than system maximum limit.
'40029': InvalidOrder, // 400, The order's leverage is too large.
'40030': InvalidOrder, // 400, The order's leverage is too small.
'40031': InvalidOrder, // 400, The deviation between current price and trigger price is too large.
'40032': InvalidOrder, // 400, The plan order's life cycle is too long.
'40033': InvalidOrder, // 400, The plan order's life cycle is too short.
'40034': BadSymbol, // 400, This contract is not found
'53002': PermissionDenied, // 403, Your account has not yet completed the kyc advanced certification, please complete first
},
'broad': {},
},
'commonCurrencies': {
'$GM': 'GOLDMINER',
'$HERO': 'Step Hero',
'$PAC': 'PAC',
'AUR': 'Aurum',
'BP': 'BEYOND',
'COT': 'Community Coin',
'CPC': 'CPCoin',
'DMS': 'DimSum', // conflict with Dragon Mainland Shards
'FOX': 'Fox Finance',
'GDT': 'Gorilla Diamond',
'GLD': 'Goldario',
'MIM': 'MIM Swarm',
'MVP': 'MVP Coin',
'ONE': 'Menlo One',
'PLA': 'Plair',
'TCT': 'TacoCat Token',
'TRU': 'Truebit', // conflict with TrueFi
'ULT': 'Ultiledger',
},
'options': {
'networks': {
'TRX': 'TRC20',
'ETH': 'ERC20',
},
'defaultNetworks': {
'USDT': 'ERC20',
},
'defaultType': 'spot', // 'spot', 'swap'
'fetchBalance': {
'type': 'spot', // 'spot', 'swap', 'contract', 'account'
},
'createMarketBuyOrderRequiresPrice': true,
},
});
}
async fetchTime (params = {}) {
const response = await this.publicSystemGetTime (params);
//
// {
// "message":"OK",
// "code":1000,
// "trace":"c4e5e5b7-fe9f-4191-89f7-53f6c5bf9030",
// "data":{
// "server_time":1599843709578
// }
// }
//
const data = this.safeValue (response, 'data', {});
return this.safeInteger (data, 'server_time');
}
async fetchStatus (params = {}) {
const options = this.safeValue (this.options, 'fetchBalance', {});
const defaultType = this.safeString (this.options, 'defaultType');
let type = this.safeString (options, 'type', defaultType);
type = this.safeString (params, 'type', type);
params = this.omit (params, 'type');
const response = await this.publicSystemGetService (params);
//
// {
// "message": "OK",
// "code": 1000,
// "trace": "1d3f28b0-763e-4f78-90c4-5e3ad19dc595",
// "data": {
// "service": [
// {
// "title": "Spot API Stop",
// "service_type": "spot",
// "status": 2,
// "start_time": 1648639069125,
// "end_time": 1648639069125
// },
// {
// "title": "Contract API Stop",
// "service_type": "contract",
// "status": 2,
// "start_time": 1648639069125,
// "end_time": 1648639069125
// }
// ]
// }
// }
//
const data = this.safeValue (response, 'data', {});
const services = this.safeValue (data, 'service', []);
const servicesByType = this.indexBy (services, 'service_type');
if ((type === 'swap') || (type === 'future')) {
type = 'contract';
}
const service = this.safeValue (servicesByType, type);
let status = undefined;
let eta = undefined;
if (service !== undefined) {
const statusCode = this.safeInteger (service, 'status');
if (statusCode === 2) {
status = 'ok';
} else {
status = 'maintenance';
eta = this.safeInteger (service, 'end_time');
}
}
return {
'status': status,
'updated': this.milliseconds (),
'eta': eta,
'url': undefined,
'info': response,
};
}
async fetchSpotMarkets (params = {}) {
const response = await this.publicSpotGetSymbolsDetails (params);
//
// {
// "message":"OK",
// "code":1000,
// "trace":"a67c9146-086d-4d3f-9897-5636a9bb26e1",
// "data":{
// "symbols":[
// {
// "symbol":"PRQ_BTC",
// "symbol_id":1232,
// "base_currency":"PRQ",
// "quote_currency":"BTC",
// "quote_increment":"1.0000000000",
// "base_min_size":"1.0000000000",
// "base_max_size":"10000000.0000000000",
// "price_min_precision":8,
// "price_max_precision":10,
// "expiration":"NA",
// "min_buy_amount":"0.0001000000",
// "min_sell_amount":"0.0001000000"
// },
// ]
// }
// }
//
const data = this.safeValue (response, 'data', {});
const symbols = this.safeValue (data, 'symbols', []);
const result = [];
for (let i = 0; i < symbols.length; i++) {
const market = symbols[i];
const id = this.safeString (market, 'symbol');
const numericId = this.safeInteger (market, 'symbol_id');
const baseId = this.safeString (market, 'base_currency');
const quoteId = this.safeString (market, 'quote_currency');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
const symbol = base + '/' + quote;
//
// https://github.com/bitmartexchange/bitmart-official-api-docs/blob/master/rest/public/symbols_details.md#response-details
// from the above API doc:
// quote_increment Minimum order price as well as the price increment
// price_min_precision Minimum price precision (digit) used to query price and kline
// price_max_precision Maximum price precision (digit) used to query price and kline
//
// the docs are wrong: https://github.com/ccxt/ccxt/issues/5612
//
const minBuyCost = this.safeString (market, 'min_buy_amount');
const minSellCost = this.safeString (market, 'min_sell_amount');
const minCost = Precise.stringMax (minBuyCost, minSellCost);
const pricePrecision = this.parsePrecision (this.safeString (market, 'price_max_precision'));
result.push ({
'id': id,
'numericId': numericId,
'symbol': symbol,
'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': true,
'contract': false,
'linear': undefined,
'inverse': undefined,
'contractSize': undefined,
'expiry': undefined,
'expiryDatetime': undefined,
'strike': undefined,
'optionType': undefined,
'precision': {
'amount': this.safeNumber (market, 'base_min_size'),
'price': this.parseNumber (pricePrecision),
},
'limits': {
'leverage': {
'min': undefined,
'max': undefined,
},
'amount': {
'min': this.safeNumber (market, 'base_min_size'),
'max': this.safeNumber (market, 'base_max_size'),
},
'price': {
'min': undefined,
'max': undefined,
},
'cost': {
'min': this.parseNumber (minCost),
'max': undefined,
},
},
'info': market,
});
}
return result;
}
async fetchContractMarkets (params = {}) {
const response = await this.publicContractGetTickers (params);
//
// {
// "message": "OK",
// "code": 1000,
// "trace": "045d13a8-4bc7-4974-9748-97d0ea183ef0",
// "data": {
// "tickers": [
// {
// "contract_symbol": "RAYUSDT",
// "last_price": "3.893",
// "index_price": "3.90248043",
// "last_funding_rate": "-0.00054285",
// "price_change_percent_24h": "-6.955",
// "volume_24h": "10450969.34602996",
// "url": "https://futures.bitmart.com/en?symbol=RAYUSDT",
// "high_price": "4.299",
// "low_price": "3.887",
// "legal_coin_price": "3.893056"
// },
// ...
// ]
// }
// }
//
const data = this.safeValue (response, 'data', {});
const tickers = this.safeValue (data, 'tickers', []);
const result = [];
for (let i = 0; i < tickers.length; i++) {
const market = tickers[i];
const id = this.safeString (market, 'contract_symbol');
const baseId = id.slice (0, -4);
const quoteId = id.slice (-4);
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
const splitId = id.split ('_');
const splitIdEnding = this.safeString (splitId, 1);
let settle = 'USDT';
let symbol = base + '/' + quote + ':' + settle;
let type = 'swap';
let swap = true;
let future = false;
let expiry = undefined;
if (splitIdEnding !== undefined) {
settle = 'BTC';
symbol = base + '/' + quote + ':' + settle;
if (splitIdEnding !== 'PERP') {
const date = this.iso8601 (this.milliseconds ());
const splitDate = date.split ('-');
const year = splitDate[0];
const shortYear = year.slice (0, 2);
const expiryMonth = splitIdEnding.slice (0, 2);
const expiryDay = splitIdEnding.slice (2, 4);
expiry = this.parse8601 (year + '-' + expiryMonth + '-' + expiryDay + 'T00:00:00Z');
symbol = symbol + '-' + shortYear + splitIdEnding;
type = 'future';
swap = false;
future = true;
}
}
result.push ({
'id': id,
'numericId': undefined,
'symbol': symbol,
'base': base,
'quote': quote,
'settle': settle,
'baseId': baseId,
'quoteId': quoteId,
'settleId': undefined,
'type': type,
'spot': false,
'margin': false,
'swap': swap,
'future': future,
'option': false,
'active': true,
'contract': true,
'linear': true,
'inverse': false,
'contractSize': undefined,
'expiry': expiry,
'expiryDatetime': this.iso8601 (expiry),
'strike': undefined,
'optionType': undefined,
'precision': {
'amount': undefined,
'price': undefined,
},
'limits': {
'leverage': {
'min': undefined,
'max': undefined,
},
'amount': {
'min': undefined,
'max': undefined,
},
'price': {
'min': undefined,
'max': undefined,
},
'cost': {
'min': undefined,
'max': undefined,
},
},
'info': market,
});
}
return result;
}
async fetchMarkets (params = {}) {
const spot = await this.fetchSpotMarkets (params);
const contract = await this.fetchContractMarkets (params);
return this.arrayConcat (spot, contract);
}
async fetchFundingFee (code, params = {}) {
await this.loadMarkets ();
const currency = this.currency (code);
const request = {
'currency': currency['id'],
};
const response = await this.privateAccountGetWithdrawCharge (this.extend (request, params));
//
// {
// message: 'OK',
// code: '1000',
// trace: '3ecc0adf-91bd-4de7-aca1-886c1122f54f',
// data: {
// today_available_withdraw_BTC: '100.0000',
// min_withdraw: '0.005',
// withdraw_precision: '8',
// withdraw_fee: '0.000500000000000000000000000000'
// }
// }
//
const data = response['data'];
const withdrawFees = {};
withdrawFees[code] = this.safeNumber (data, 'withdraw_fee');
return {
'info': response,
'withdraw': withdrawFees,
'deposit': {},
};
}
parseTicker (ticker, market = undefined) {
//
// spot
//
// {
// "symbol":"DOGE_USDT",
// "last_price":"0.128300",
// "quote_volume_24h":"2296619.060420",
// "base_volume_24h":"17508866.000000000000000000000000000000",
// "high_24h":"0.133900",
// "low_24h":"0.127799",
// "open_24h":"0.133100",
// "close_24h":"0.128300",
// "best_ask":"0.128530",
// "best_ask_size":"15170",
// "best_bid":"0.128200",
// "best_bid_size":"21232",
// "fluctuation":"-0.0361",
// "s_t": 1610936002, // ws only
// "url":"https://www.bitmart.com/trade?symbol=DOGE_USDT"
// }
//
// contract
//
// {
// "contract_symbol":"DOGEUSDT",
// "last_price":"0.130340",
// "index_price":"0.13048245",
// "last_funding_rate":"0.00002287",
// "price_change_percent_24h":"-2.074",
// "volume_24h":"113705028.59482228",
// "url":"https://futures.bitmart.com/en?symbol=DOGEUSDT",
// "high_price":"0.134520",
// "low_price":"0.128570",
// "legal_coin_price":"0.1302699"
// }
//
const timestamp = this.safeTimestamp2 (ticker, 'timestamp', 's_t', this.milliseconds ());
const marketId = this.safeString2 (ticker, 'symbol', 'contract_symbol');
market = this.safeMarket (marketId, market);
const symbol = market['symbol'];
const last = this.safeString2 (ticker, 'close_24h', 'last_price');
let percentage = this.safeString (ticker, 'price_change_percent_24h');
if (percentage === undefined) {
const percentageRaw = this.safeString (ticker, 'fluctuation');
if ((percentageRaw !== undefined) && (percentageRaw !== '0')) { // a few tickers show strictly '0' in fluctuation field
const direction = percentageRaw[0];
percentage = direction + Precise.stringMul (percentageRaw.replace (direction, ''), '100');
} else if (percentageRaw === '0') {
percentage = '0';
}
}
const baseVolume = this.safeString (ticker, 'base_volume_24h');
let quoteVolume = this.safeString (ticker, 'quote_volume_24h');
quoteVolume = this.safeString (ticker, 'volume_24h', quoteVolume);
const average = this.safeString2 (ticker, 'avg_price', 'index_price');
const price = this.safeString (ticker, 'depth_price', ticker);
const high = this.safeString2 (ticker, 'high_24h', 'high_price');
const low = this.safeString2 (ticker, 'low_24h', 'low_price');
return this.safeTicker ({
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'high': high,
'low': low,
'bid': this.safeString (price, 'best_bid'),
'bidVolume': this.safeString (ticker, 'best_bid_size'),
'ask': this.safeString (price, 'best_ask'),
'askVolume': this.safeString (ticker, 'best_ask_size'),
'vwap': undefined,
'open': this.safeString (ticker, 'open_24h'),
'close': last,
'last': last,
'previousClose': undefined,
'change': undefined,
'percentage': percentage,
'average': average,
'baseVolume': baseVolume,
'quoteVolume': quoteVolume,
'info': ticker,
}, market, false);
}
async fetchTicker (symbol, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
const request = {};
let method = undefined;
if (market['swap'] || market['future']) {
method = 'publicContractGetTickers';
request['contract_symbol'] = market['id'];
} else if (market['spot']) {
method = 'publicSpotGetTicker';
request['symbol'] = market['id'];
}
const response = await this[method] (this.extend (request, params));
//
// spot
//
// {
// "message":"OK",
// "code":1000,
// "trace":"6aa5b923-2f57-46e3-876d-feca190e0b82",
// "data":{
// "tickers":[
// {
// "symbol":"ETH_BTC",
// "last_price":"0.036037",
// "quote_volume_24h":"4380.6660000000",
// "base_volume_24h":"159.3582006712",
// "high_24h":"0.036972",
// "low_24h":"0.035524",
// "open_24h":"0.036561",
// "close_24h":"0.036037",
// "best_ask":"0.036077",
// "best_ask_size":"9.9500",
// "best_bid":"0.035983",
// "best_bid_size":"4.2792",
// "fluctuation":"-0.0143",
// "url":"https://www.bitmart.com/trade?symbol=ETH_BTC"
// }
// ]
// }
// }
//
// contract
//
// {
// "message":"OK",
// "code":1000,
// "trace":"4a0ebceb-d3f7-45a3-8feb-f61e230e24cd",
// "data":{
// "tickers":[
// {
// "contract_symbol":"DOGEUSDT",
// "last_price":"0.130180",
// "index_price":"0.13028635",
// "last_funding_rate":"0.00002025",
// "price_change_percent_24h":"-2.326",
// "volume_24h":"116789313.01797258",
// "url":"https://futures.bitmart.com/en?symbol=DOGEUSDT",
// "high_price":"0.134520",
// "low_price":"0.128570",
// "legal_coin_price":"0.13017401"
// }
// ]
// }
// }
//
const data = this.safeValue (response, 'data', {});
const tickers = this.safeValue (data, 'tickers', []);
// fails in naming for contract tickers 'contract_symbol'
let tickersById = undefined;
if (market['spot']) {
tickersById = this.indexBy (tickers, 'symbol');
} else if (market['swap'] || market['future']) {
tickersById = this.indexBy (tickers, 'contract_symbol');
}
const ticker = this.safeValue (tickersById, market['id']);
return this.parseTicker (ticker, market);
}
async fetchTickers (symbols = undefined, params = {}) {
await this.loadMarkets ();
const [ marketType, query ] = this.handleMarketTypeAndParams ('fetchTickers', undefined, params);
const method = this.getSupportedMapping (marketType, {
'spot': 'publicSpotGetTicker',
'swap': 'publicContractGetTickers',
'future': 'publicContractGetTickers',
});
const response = await this[method] (query);
const data = this.safeValue (response, 'data', {});
const tickers = this.safeValue (data, 'tickers', []);
const result = {};
for (let i = 0; i < tickers.length; i++) {
const ticker = this.parseTicker (tickers[i]);
const symbol = ticker['symbol'];
result[symbol] = ticker;
}
return this.filterByArray (result, 'symbol', symbols);
}
async fetchCurrencies (params = {}) {
const response = await this.publicAccountGetCurrencies (params);
//
// {
// "message":"OK",
// "code":1000,
// "trace":"8c768b3c-025f-413f-bec5-6d6411d46883",
// "data":{
// "currencies":[
// {"currency":"MATIC","name":"Matic Network","withdraw_enabled":true,"deposit_enabled":true},
// {"currency":"KTN","name":"Kasoutuuka News","withdraw_enabled":true,"deposit_enabled":false},
// {"currency":"BRT","name":"Berith","withdraw_enabled":true,"deposit_enabled":true},
// ]
// }
// }
//
const data = this.safeValue (response, 'data', {});
const currencies = this.safeValue (data, 'currencies', []);
const result = {};
for (let i = 0; i < currencies.length; i++) {
const currency = currencies[i];
const id = this.safeString (currency, 'currency');
const code = this.safeCurrencyCode (id);
const name = this.safeString (currency, 'name');
const withdrawEnabled = this.safeValue (currency, 'withdraw_enabled');
const depositEnabled = this.safeValue (currency, 'deposit_enabled');
const active = withdrawEnabled && depositEnabled;
result[code] = {
'id': id,
'code': code,
'name': name,
'info': currency, // the original payload
'active': active,
'deposit': depositEnabled,
'withdraw': withdrawEnabled,
'fee': undefined,
'precision': undefined,
'limits': {
'amount': { 'min': undefined, 'max': undefined },
'withdraw': { 'min': undefined, 'max': undefined },
},
};
}
return result;
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
const request = {};
let method = undefined;
if (market['spot']) {
method = 'publicSpotGetSymbolsBook';
request['symbol'] = market['id'];
if (limit !== undefined) {
request['size'] = limit; // default 50, max 200
}
// request['precision'] = 4; // optional price precision / depth level whose range is defined in symbol details
} else if (market['swap'] || market['future']) {
method = 'publicContractGetDepth';
request['contractID'] = market['id'];
if (limit !== undefined) {
request['count'] = limit; // returns all records if size is omitted
}
}
const response = await this[method] (this.extend (request, params));
//
// spot
//
// {
// "message":"OK",
// "code":1000,
// "trace":"8254f8fc-431d-404f-ad9a-e716339f66c7",
// "data":{
// "buys":[
// {"amount":"4.7091","total":"4.71","price":"0.034047","count":"1"},
// {"amount":"5.7439","total":"10.45","price":"0.034039","count":"1"},
// {"amount":"2.5249","total":"12.98","price":"0.032937","count":"1"},
// ],
// "sells":[
// {"amount":"41.4365","total":"41.44","price":"0.034174","count":"1"},
// {"amount":"4.2317","total":"45.67","price":"0.034183","count":"1"},
// {"amount":"0.3000","total":"45.97","price":"0.034240","count":"1"},
// ]
// }
// }
//
// contract
//
// {
// "errno":"OK",
// "message":"OK",
// "code":1000,
// "trace":"c330dfca-ca5b-4f15-b350-9fef3f049b4f",
// "data":{
// "sells":[
// {"price":"347.6","vol":"6678"},
// {"price":"347.7","vol":"3452"},
// {"price":"347.8","vol":"6331"},
// ],
// "buys":[
// {"price":"347.5","vol":"6222"},
// {"price":"347.4","vol":"20979"},
// {"price":"347.3","vol":"15179"},
// ]
// }
// }
//
const data = this.safeValue (response, 'data', {});
if (market['spot']) {
return this.parseOrderBook (data, symbol, undefined, 'buys', 'sells', 'price', 'amount');
} else if (market['swap'] || market['future']) {
return this.parseOrderBook (data, symbol, undefined, 'buys', 'sells', 'price', 'vol');
}
}
parseTrade (trade, market = undefined) {
//
// public fetchTrades spot ( amount = count * price )
//
// {
// "amount": "818.94",
// "order_time": "1637601839035", // ETH/USDT
// "price": "4221.99",
// "count": "0.19397",
// "type": "buy"
// }
//
// public fetchTrades contract, private fetchMyTrades contract
//
// {
// "order_id":109159616160,
// "trade_id":109159616197,
// "contract_id":2,
// "deal_price":"347.6",
// "deal_vol":"5623",
// "make_fee":"-5.8636644",
// "take_fee":"9.772774",
// "created_at":"2020-09-09T11:49:50.749170536Z",
// "way":1,
// "fluctuation":"0"
// }
//
// private fetchMyTrades spot
//
// {
// "detail_id":256348632,
// "order_id":2147484350,
// "symbol":"BTC_USDT",
// "create_time":1590462303000,
// "side":"buy",
// "fees":"0.00001350",
// "fee_coin_name":"BTC",
// "notional":"88.00000000",
// "price_avg":"8800.00",
// "size":"0.01000",
// "exec_type":"M"
// }
//
const id = this.safeString2 (trade, 'trade_id', 'detail_id');
let timestamp = this.safeInteger2 (trade, 'order_time', 'create_time');
if (timestamp === undefined) {
timestamp = this.safeTimestamp (trade, 's_t');
}
if (timestamp === undefined) {
timestamp = this.parse8601 (this.safeString (trade, 'created_at'));
}
const type = undefined;
const way = this.safeInteger (trade, 'way');
let side = this.safeStringLower2 (trade, 'type', 'side');
if ((side === undefined) && (way !== undefined)) {
if (way < 5) {
side = 'buy';
} else {
side = 'sell';
}
}
let takerOrMaker = undefined;
const execType = this.safeString (trade, 'exec_type');
if (execType !== undefined) {
takerOrMaker = (execType === 'M') ? 'maker' : 'taker';
}
let priceString = this.safeString2 (trade, 'price', 'deal_price');
priceString = this.safeString (trade, 'price_avg', priceString);
let amountString = this.safeString2 (trade, 'count', 'deal_vol');
amountString = this.safeString (trade, 'size', amountString);
const costString = this.safeString2 (trade, 'amount', 'notional');
const orderId = this.safeInteger (trade, 'order_id');
const marketId = this.safeString2 (trade, 'contract_id', 'symbol');
market = this.safeMarket (marketId, market, '_');
const feeCostString = this.safeString (trade, 'fees');
let fee = undefined;
if (feeCostString !== undefined) {
const feeCurrencyId = this.safeString (trade, 'fee_coin_name');
let feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId);
if (feeCurrencyCode === undefined) {
feeCurrencyCode = (side === 'buy') ? market['base'] : market['quote'];
}
fee = {
'cost': feeCostString,
'currency': feeCurrencyCode,
};
}
return this.safeTrade ({
'info': trade,
'id': id,
'order': orderId,
'timestamp': timestamp