UNPKG

ccxt

Version:

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

1,171 lines (1,169 loc) • 71.8 kB
// ---------------------------------------------------------------------------- // PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN: // https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code // EDIT THE CORRESPONDENT .ts FILE INSTEAD // --------------------------------------------------------------------------- import bitmartRest from '../bitmart.js'; import { AuthenticationError, ExchangeError, NotSupported } from '../base/errors.js'; import { ArrayCache, ArrayCacheByTimestamp, ArrayCacheBySymbolById, ArrayCacheBySymbolBySide } from '../base/ws/Cache.js'; import { sha256 } from '../static_dependencies/noble-hashes/sha256.js'; import { Asks, Bids } from '../base/ws/OrderBookSide.js'; // --------------------------------------------------------------------------- export default class bitmart extends bitmartRest { describe() { return this.deepExtend(super.describe(), { 'has': { 'createOrderWs': false, 'editOrderWs': false, 'fetchOpenOrdersWs': false, 'fetchOrderWs': false, 'cancelOrderWs': false, 'cancelOrdersWs': false, 'cancelAllOrdersWs': false, 'ws': true, 'watchBalance': true, 'watchTicker': true, 'watchTickers': true, 'watchBidsAsks': true, 'watchOrderBook': true, 'watchOrderBookForSymbols': true, 'watchOrders': true, 'watchTrades': true, 'watchTradesForSymbols': true, 'watchOHLCV': true, 'watchPosition': 'emulated', 'watchPositions': true, }, 'urls': { 'api': { 'ws': { 'spot': { 'public': 'wss://ws-manager-compress.{hostname}/api?protocol=1.1', 'private': 'wss://ws-manager-compress.{hostname}/user?protocol=1.1', }, 'swap': { 'public': 'wss://openapi-ws-v2.{hostname}/api?protocol=1.1', 'private': 'wss://openapi-ws-v2.{hostname}/user?protocol=1.1', }, }, }, }, 'options': { 'defaultType': 'spot', 'watchBalance': { 'fetchBalanceSnapshot': true, 'awaitBalanceSnapshot': false, // whether to wait for the balance snapshot before providing updates }, // // orderbook channels can have: // - 'depth5', 'depth20', 'depth50' // these endpoints emit full Orderbooks once in every 500ms // - 'depth/increase100' // this endpoint is preferred, because it emits once in 100ms. however, when this value is chosen, it only affects spot-market, but contracts markets automatically `depth50` will be being used 'watchOrderBook': { 'depth': 'depth/increase100', }, 'watchOrderBookForSymbols': { 'depth': 'depth/increase100', }, 'watchTrades': { 'ignoreDuplicates': true, }, 'ws': { 'inflate': true, }, 'timeframes': { '1m': '1m', '3m': '3m', '5m': '5m', '15m': '15m', '30m': '30m', '45m': '45m', '1h': '1H', '2h': '2H', '3h': '3H', '4h': '4H', '1d': '1D', '1w': '1W', '1M': '1M', }, }, 'streaming': { 'keepAlive': 15000, }, }); } async subscribe(channel, symbol, type, params = {}) { const market = this.market(symbol); const url = this.implodeHostname(this.urls['api']['ws'][type]['public']); let request = {}; let messageHash = undefined; if (type === 'spot') { messageHash = 'spot/' + channel + ':' + market['id']; request = { 'op': 'subscribe', 'args': [messageHash], }; } else { messageHash = 'futures/' + channel + ':' + market['id']; const speed = this.safeString(params, 'speed'); if (speed !== undefined) { params = this.omit(params, 'speed'); messageHash += ':' + speed; } request = { 'action': 'subscribe', 'args': [messageHash], }; } return await this.watch(url, messageHash, this.deepExtend(request, params), messageHash); } async subscribeMultiple(channel, type, symbols = undefined, params = {}) { symbols = this.marketSymbols(symbols, type, false, true); const url = this.implodeHostname(this.urls['api']['ws'][type]['public']); const channelType = (type === 'spot') ? 'spot' : 'futures'; const actionType = (type === 'spot') ? 'op' : 'action'; const rawSubscriptions = []; const messageHashes = []; for (let i = 0; i < symbols.length; i++) { const market = this.market(symbols[i]); const message = channelType + '/' + channel + ':' + market['id']; rawSubscriptions.push(message); messageHashes.push(channel + ':' + market['symbol']); } // as an exclusion, futures "tickers" need one generic request for all symbols // if ((type !== 'spot') && (channel === 'ticker')) { // rawSubscriptions = [ channelType + '/' + channel ]; // } // Exchange update from 2025-02-11 supports subscription by trading pair for swap const request = { 'args': rawSubscriptions, }; request[actionType] = 'subscribe'; return await this.watchMultiple(url, messageHashes, this.deepExtend(request, params), rawSubscriptions); } /** * @method * @name bitmart#watchBalance * @see https://developer-pro.bitmart.com/en/spot/#private-balance-change * @see https://developer-pro.bitmart.com/en/futuresv2/#private-assets-channel * @description watch balance and get the amount of funds available for trading or funds locked in orders * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [balance structure]{@link https://docs.ccxt.com/#/?id=balance-structure} */ async watchBalance(params = {}) { await this.loadMarkets(); let type = 'spot'; [type, params] = this.handleMarketTypeAndParams('watchBalance', undefined, params); await this.authenticate(type, params); let request = {}; if (type === 'spot') { request = { 'op': 'subscribe', 'args': ['spot/user/balance:BALANCE_UPDATE'], }; } else { request = { 'action': 'subscribe', 'args': ['futures/asset:USDT', 'futures/asset:BTC', 'futures/asset:ETH'], }; } const messageHash = 'balance:' + type; const url = this.implodeHostname(this.urls['api']['ws'][type]['private']); const client = this.client(url); this.setBalanceCache(client, type, messageHash); let fetchBalanceSnapshot = undefined; let awaitBalanceSnapshot = undefined; [fetchBalanceSnapshot, params] = this.handleOptionAndParams(this.options, 'watchBalance', 'fetchBalanceSnapshot', true); [awaitBalanceSnapshot, params] = this.handleOptionAndParams(this.options, 'watchBalance', 'awaitBalanceSnapshot', false); if (fetchBalanceSnapshot && awaitBalanceSnapshot) { await client.future(type + ':fetchBalanceSnapshot'); } return await this.watch(url, messageHash, this.deepExtend(request, params), messageHash); } setBalanceCache(client, type, subscribeHash) { if (subscribeHash in client.subscriptions) { return; } const options = this.safeValue(this.options, 'watchBalance'); const snapshot = this.safeBool(options, 'fetchBalanceSnapshot', true); if (snapshot) { const messageHash = type + ':' + 'fetchBalanceSnapshot'; if (!(messageHash in client.futures)) { client.future(messageHash); this.spawn(this.loadBalanceSnapshot, client, messageHash, type); } } this.balance[type] = {}; // without this comment, transpilation breaks for some reason... } async loadBalanceSnapshot(client, messageHash, type) { const response = await this.fetchBalance({ 'type': type }); this.balance[type] = this.extend(response, this.safeValue(this.balance, type, {})); // don't remove the future from the .futures cache const future = client.futures[messageHash]; future.resolve(); client.resolve(this.balance[type], 'balance:' + type); } handleBalance(client, message) { // // spot // { // "data":[ // { // "balance_details":[ // { // "av_bal":"0.206000000000000000000000000000", // "ccy":"LTC", // "fz_bal":"0.100000000000000000000000000000" // } // ], // "event_time":"1701632345415", // "event_type":"TRANSACTION_COMPLETED" // } // ], // "table":"spot/user/balance" // } // swap // { // group: 'futures/asset:USDT', // data: { // currency: 'USDT', // available_balance: '37.19688649135', // position_deposit: '0.788687546', // frozen_balance: '0' // } // } // const channel = this.safeString2(message, 'table', 'group'); const data = this.safeValue(message, 'data'); if (data === undefined) { return; } const isSpot = (channel.indexOf('spot') >= 0); const type = isSpot ? 'spot' : 'swap'; this.balance[type]['info'] = message; if (isSpot) { if (!Array.isArray(data)) { return; } for (let i = 0; i < data.length; i++) { const timestamp = this.safeInteger(message, 'event_time'); this.balance[type]['timestamp'] = timestamp; this.balance[type]['datetime'] = this.iso8601(timestamp); const balanceDetails = this.safeValue(data[i], 'balance_details', []); for (let ii = 0; ii < balanceDetails.length; ii++) { const rawBalance = balanceDetails[i]; const account = this.account(); const currencyId = this.safeString(rawBalance, 'ccy'); const code = this.safeCurrencyCode(currencyId); account['free'] = this.safeString(rawBalance, 'av_bal'); account['used'] = this.safeString(rawBalance, 'fz_bal'); this.balance[type][code] = account; } } } else { const currencyId = this.safeString(data, 'currency'); const code = this.safeCurrencyCode(currencyId); const account = this.account(); account['free'] = this.safeString(data, 'available_balance'); account['used'] = this.safeString(data, 'frozen_balance'); this.balance[type][code] = account; } this.balance[type] = this.safeBalance(this.balance[type]); const messageHash = 'balance:' + type; client.resolve(this.balance[type], messageHash); } /** * @method * @name bitmart#watchTrades * @see https://developer-pro.bitmart.com/en/spot/#public-trade-channel * @see https://developer-pro.bitmart.com/en/futuresv2/#public-trade-channel * @description get the list of most recent trades for a particular symbol * @param {string} symbol unified symbol of the market to fetch trades for * @param {int} [since] timestamp in ms of the earliest trade to fetch * @param {int} [limit] the maximum amount of trades to fetch * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades} */ async watchTrades(symbol, since = undefined, limit = undefined, params = {}) { return await this.watchTradesForSymbols([symbol], since, limit, params); } /** * @method * @name bitmart#watchTradesForSymbols * @see https://developer-pro.bitmart.com/en/spot/#public-trade-channel * @description get the list of most recent trades for a list of symbols * @param {string[]} symbols unified symbol of the market to fetch trades for * @param {int} [since] timestamp in ms of the earliest trade to fetch * @param {int} [limit] the maximum amount of trades to fetch * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades} */ async watchTradesForSymbols(symbols, since = undefined, limit = undefined, params = {}) { await this.loadMarkets(); let marketType = undefined; [symbols, marketType, params] = this.getParamsForMultipleSub('watchTradesForSymbols', symbols, limit, params); const channelName = 'trade'; const trades = await this.subscribeMultiple(channelName, marketType, symbols, params); if (this.newUpdates) { const first = this.safeDict(trades, 0); const tradeSymbol = this.safeString(first, 'symbol'); limit = trades.getLimit(tradeSymbol, limit); } const result = this.filterBySinceLimit(trades, since, limit, 'timestamp', true); if (this.handleOption('watchTrades', 'ignoreDuplicates', true)) { let filtered = this.removeRepeatedTradesFromArray(result); filtered = this.sortBy(filtered, 'timestamp'); return filtered; } return result; } getParamsForMultipleSub(methodName, symbols, limit = undefined, params = {}) { symbols = this.marketSymbols(symbols, undefined, false, true); const length = symbols.length; if (length > 20) { throw new NotSupported(this.id + ' ' + methodName + '() accepts a maximum of 20 symbols in one request'); } const market = this.market(symbols[0]); let marketType = undefined; [marketType, params] = this.handleMarketTypeAndParams(methodName, market, params); return [symbols, marketType, params]; } /** * @method * @name bitmart#watchTicker * @see https://developer-pro.bitmart.com/en/spot/#public-ticker-channel * @see https://developer-pro.bitmart.com/en/futuresv2/#public-ticker-channel * @description watches 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 exchange API endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ async watchTicker(symbol, params = {}) { await this.loadMarkets(); symbol = this.symbol(symbol); const tickers = await this.watchTickers([symbol], params); return tickers[symbol]; } /** * @method * @name bitmart#watchTickers * @see https://developer-pro.bitmart.com/en/spot/#public-ticker-channel * @see https://developer-pro.bitmart.com/en/futuresv2/#public-ticker-channel * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list * @param {string[]} symbols unified symbol of the market to fetch the ticker for * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ async watchTickers(symbols = undefined, params = {}) { await this.loadMarkets(); const market = this.getMarketFromSymbols(symbols); let marketType = undefined; [marketType, params] = this.handleMarketTypeAndParams('watchTickers', market, params); const ticker = await this.subscribeMultiple('ticker', marketType, symbols, params); if (this.newUpdates) { const tickers = {}; tickers[ticker['symbol']] = ticker; return tickers; } return this.filterByArray(this.tickers, 'symbol', symbols); } /** * @method * @name bitmart#watchBidsAsks * @see https://developer-pro.bitmart.com/en/spot/#public-ticker-channel * @see https://developer-pro.bitmart.com/en/futuresv2/#public-ticker-channel * @description watches best bid & ask for symbols * @param {string[]} symbols unified symbol of the market to fetch the ticker for * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ async watchBidsAsks(symbols = undefined, params = {}) { await this.loadMarkets(); symbols = this.marketSymbols(symbols, undefined, false); const firstMarket = this.getMarketFromSymbols(symbols); let marketType = undefined; [marketType, params] = this.handleMarketTypeAndParams('watchBidsAsks', firstMarket, params); const url = this.implodeHostname(this.urls['api']['ws'][marketType]['public']); const channelType = (marketType === 'spot') ? 'spot' : 'futures'; const actionType = (marketType === 'spot') ? 'op' : 'action'; let rawSubscriptions = []; const messageHashes = []; for (let i = 0; i < symbols.length; i++) { const market = this.market(symbols[i]); rawSubscriptions.push(channelType + '/ticker:' + market['id']); messageHashes.push('bidask:' + symbols[i]); } if (marketType !== 'spot') { rawSubscriptions = [channelType + '/ticker']; } const request = { 'args': rawSubscriptions, }; request[actionType] = 'subscribe'; const newTickers = await this.watchMultiple(url, messageHashes, request, rawSubscriptions); if (this.newUpdates) { const tickers = {}; tickers[newTickers['symbol']] = newTickers; return tickers; } return this.filterByArray(this.bidsasks, 'symbol', symbols); } handleBidAsk(client, message) { const table = this.safeString(message, 'table'); const isSpot = (table !== undefined); let rawTickers = []; if (isSpot) { rawTickers = this.safeList(message, 'data', []); } else { rawTickers = [this.safeValue(message, 'data', {})]; } if (!rawTickers.length) { return; } for (let i = 0; i < rawTickers.length; i++) { const ticker = this.parseWsBidAsk(rawTickers[i]); const symbol = ticker['symbol']; this.bidsasks[symbol] = ticker; const messageHash = 'bidask:' + symbol; client.resolve(ticker, messageHash); } } parseWsBidAsk(ticker, market = undefined) { const marketId = this.safeString(ticker, 'symbol'); market = this.safeMarket(marketId, market); const symbol = this.safeString(market, 'symbol'); const timestamp = this.safeInteger(ticker, 'ms_t'); return this.safeTicker({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'ask': this.safeString2(ticker, 'ask_px', 'ask_price'), 'askVolume': this.safeString2(ticker, 'ask_sz', 'ask_vol'), 'bid': this.safeString2(ticker, 'bid_px', 'bid_price'), 'bidVolume': this.safeString2(ticker, 'bid_sz', 'bid_vol'), 'info': ticker, }, market); } /** * @method * @name bitmart#watchOrders * @description watches information on multiple orders made by the user * @see https://developer-pro.bitmart.com/en/spot/#private-order-progress * @see https://developer-pro.bitmart.com/en/futuresv2/#private-order-channel * @param {string} symbol unified market symbol of the market orders were made in * @param {int} [since] the earliest time in ms to fetch orders for * @param {int} [limit] the maximum number of order structures to retrieve * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure} */ async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets(); let market = undefined; let messageHash = 'orders'; if (symbol !== undefined) { symbol = this.symbol(symbol); market = this.market(symbol); messageHash = 'orders::' + symbol; } let type = 'spot'; [type, params] = this.handleMarketTypeAndParams('watchOrders', market, params); await this.authenticate(type, params); let request = undefined; if (type === 'spot') { let argsRequest = 'spot/user/order:'; if (symbol !== undefined) { argsRequest += market['id']; } else { argsRequest = 'spot/user/orders:ALL_SYMBOLS'; } request = { 'op': 'subscribe', 'args': [argsRequest], }; } else { request = { 'action': 'subscribe', 'args': ['futures/order'], }; } const url = this.implodeHostname(this.urls['api']['ws'][type]['private']); const newOrders = await this.watch(url, messageHash, this.deepExtend(request, params), messageHash); if (this.newUpdates) { return newOrders; } return this.filterBySymbolSinceLimit(this.orders, symbol, since, limit, true); } handleOrders(client, message) { // // spot // { // "data":[ // { // "symbol": "LTC_USDT", // "notional": '', // "side": "buy", // "last_fill_time": "0", // "ms_t": "1646216634000", // "type": "limit", // "filled_notional": "0.000000000000000000000000000000", // "last_fill_price": "0", // "size": "0.500000000000000000000000000000", // "price": "50.000000000000000000000000000000", // "last_fill_count": "0", // "filled_size": "0.000000000000000000000000000000", // "margin_trading": "0", // "state": "8", // "order_id": "24807076628", // "order_type": "0" // } // ], // "table":"spot/user/order" // } // swap // { // "group":"futures/order", // "data":[ // { // "action":2, // "order":{ // "order_id":"2312045036986775", // "client_order_id":"", // "price":"71.61707928", // "size":"1", // "symbol":"LTCUSDT", // "state":1, // "side":4, // "type":"market", // "leverage":"1", // "open_type":"cross", // "deal_avg_price":"0", // "deal_size":"0", // "create_time":1701625324646, // "update_time":1701625324640, // "plan_order_id":"", // "last_trade":null // } // } // ] // } // const orders = this.safeValue(message, 'data'); if (orders === undefined) { return; } const ordersLength = orders.length; const newOrders = []; const symbols = {}; if (ordersLength > 0) { const limit = this.safeInteger(this.options, 'ordersLimit', 1000); if (this.orders === undefined) { this.orders = new ArrayCacheBySymbolById(limit); } const stored = this.orders; for (let i = 0; i < orders.length; i++) { const order = this.parseWsOrder(orders[i]); stored.append(order); newOrders.push(order); const symbol = order['symbol']; symbols[symbol] = true; } } const messageHash = 'orders'; const symbolKeys = Object.keys(symbols); for (let i = 0; i < symbolKeys.length; i++) { const symbol = symbolKeys[i]; const symbolSpecificMessageHash = messageHash + '::' + symbol; client.resolve(newOrders, symbolSpecificMessageHash); } client.resolve(newOrders, messageHash); } parseWsOrder(order, market = undefined) { // // spot // { // "symbol": "LTC_USDT", // "notional": '', // "side": "buy", // "last_fill_time": "0", // "ms_t": "1646216634000", // "type": "limit", // "filled_notional": "0.000000000000000000000000000000", // "last_fill_price": "0", // "size": "0.500000000000000000000000000000", // "price": "50.000000000000000000000000000000", // "last_fill_count": "0", // "filled_size": "0.000000000000000000000000000000", // "margin_trading": "0", // "state": "8", // "order_id": "24807076628", // "order_type": "0" // } // swap // { // "action":2, // "order":{ // "order_id":"2312045036986775", // "client_order_id":"", // "price":"71.61707928", // "size":"1", // "symbol":"LTCUSDT", // "state":1, // "side":4, // "type":"market", // "leverage":"1", // "open_type":"cross", // "deal_avg_price":"0", // "deal_size":"0", // "create_time":1701625324646, // "update_time":1701625324640, // "plan_order_id":"", // "last_trade":null // } // } // const action = this.safeNumber(order, 'action'); const isSpot = (action === undefined); if (isSpot) { const marketId = this.safeString(order, 'symbol'); market = this.safeMarket(marketId, market, '_', 'spot'); const id = this.safeString(order, 'order_id'); const clientOrderId = this.safeString(order, 'clientOid'); const price = this.safeString(order, 'price'); const filled = this.safeString(order, 'filled_size'); const amount = this.safeString(order, 'size'); const type = this.safeString(order, 'type'); const rawState = this.safeString(order, 'state'); const status = this.parseOrderStatusByType(market['type'], rawState); const timestamp = this.safeInteger(order, 'ms_t'); const symbol = market['symbol']; const side = this.safeStringLower(order, 'side'); return this.safeOrder({ 'info': order, 'symbol': symbol, 'id': id, 'clientOrderId': clientOrderId, 'timestamp': undefined, 'datetime': undefined, 'lastTradeTimestamp': timestamp, 'type': type, 'timeInForce': undefined, 'postOnly': undefined, 'side': side, 'price': price, 'stopPrice': undefined, 'triggerPrice': undefined, 'amount': amount, 'cost': undefined, 'average': undefined, 'filled': filled, 'remaining': undefined, 'status': status, 'fee': undefined, 'trades': undefined, }, market); } else { const orderInfo = this.safeValue(order, 'order'); const marketId = this.safeString(orderInfo, 'symbol'); const symbol = this.safeSymbol(marketId, market, '', 'swap'); const orderId = this.safeString(orderInfo, 'order_id'); const timestamp = this.safeInteger(orderInfo, 'create_time'); const updatedTimestamp = this.safeInteger(orderInfo, 'update_time'); const lastTrade = this.safeValue(orderInfo, 'last_trade'); const cachedOrders = this.orders; const orders = this.safeValue(cachedOrders.hashmap, symbol, {}); const cachedOrder = this.safeValue(orders, orderId); let trades = undefined; if (cachedOrder !== undefined) { trades = this.safeValue(order, 'trades'); } if (lastTrade !== undefined) { if (trades === undefined) { trades = []; } trades.push(lastTrade); } return this.safeOrder({ 'info': order, 'symbol': symbol, 'id': orderId, 'clientOrderId': this.safeString(orderInfo, 'client_order_id'), 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'lastTradeTimestamp': updatedTimestamp, 'type': this.safeString(orderInfo, 'type'), 'timeInForce': undefined, 'postOnly': undefined, 'side': this.parseWsOrderSide(this.safeString(orderInfo, 'side')), 'price': this.safeString(orderInfo, 'price'), 'stopPrice': undefined, 'triggerPrice': undefined, 'amount': this.safeString(orderInfo, 'size'), 'cost': undefined, 'average': this.safeString(orderInfo, 'deal_avg_price'), 'filled': this.safeString(orderInfo, 'deal_size'), 'remaining': undefined, 'status': this.parseWsOrderStatus(this.safeString(order, 'action')), 'fee': undefined, 'trades': trades, }, market); } } parseWsOrderStatus(statusId) { const statuses = { '1': 'closed', '2': 'open', '3': 'canceled', '4': 'closed', '5': 'canceled', '6': 'open', '7': 'open', '8': 'closed', '9': 'closed', // active adl match deal }; return this.safeString(statuses, statusId, statusId); } parseWsOrderSide(sideId) { const sides = { '1': 'buy', '2': 'buy', '3': 'sell', '4': 'sell', // sell_open_short }; return this.safeString(sides, sideId, sideId); } /** * @method * @name bitmart#watchPositions * @see https://developer-pro.bitmart.com/en/futures/#private-position-channel * @description watch all open positions * @param {string[]|undefined} symbols list of unified market symbols * @param {int} [since] the earliest time in ms to fetch positions * @param {int} [limit] the maximum number of positions to retrieve * @param {object} params extra parameters specific to the exchange API endpoint * @returns {object[]} a list of [position structure]{@link https://docs.ccxt.com/en/latest/manual.html#position-structure} */ async watchPositions(symbols = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets(); const type = 'swap'; await this.authenticate(type, params); symbols = this.marketSymbols(symbols, 'swap', true, true, false); let messageHash = 'positions'; if (symbols !== undefined) { messageHash += '::' + symbols.join(','); } const subscriptionHash = 'futures/position'; const request = { 'action': 'subscribe', 'args': ['futures/position'], }; const url = this.implodeHostname(this.urls['api']['ws'][type]['private']); const newPositions = await this.watch(url, messageHash, this.deepExtend(request, params), subscriptionHash); if (this.newUpdates) { return newPositions; } return this.filterBySymbolsSinceLimit(this.positions, symbols, since, limit); } handlePositions(client, message) { // // { // "group":"futures/position", // "data":[ // { // "symbol":"LTCUSDT", // "hold_volume":"5", // "position_type":2, // "open_type":2, // "frozen_volume":"0", // "close_volume":"0", // "hold_avg_price":"71.582", // "close_avg_price":"0", // "open_avg_price":"71.582", // "liquidate_price":"0", // "create_time":1701623327513, // "update_time":1701627620439 // }, // { // "symbol":"LTCUSDT", // "hold_volume":"6", // "position_type":1, // "open_type":2, // "frozen_volume":"0", // "close_volume":"0", // "hold_avg_price":"71.681666666666666667", // "close_avg_price":"0", // "open_avg_price":"71.681666666666666667", // "liquidate_price":"0", // "create_time":1701621167225, // "update_time":1701628152614 // } // ] // } // const data = this.safeValue(message, 'data', []); if (this.positions === undefined) { this.positions = new ArrayCacheBySymbolBySide(); } const cache = this.positions; const newPositions = []; for (let i = 0; i < data.length; i++) { const rawPosition = data[i]; const position = this.parseWsPosition(rawPosition); newPositions.push(position); cache.append(position); } const messageHashes = this.findMessageHashes(client, 'positions::'); for (let i = 0; i < messageHashes.length; i++) { const messageHash = messageHashes[i]; const parts = messageHash.split('::'); const symbolsString = parts[1]; const symbols = symbolsString.split(','); const positions = this.filterByArray(newPositions, 'symbol', symbols, false); if (!this.isEmpty(positions)) { client.resolve(positions, messageHash); } } client.resolve(newPositions, 'positions'); } parseWsPosition(position, market = undefined) { // // { // "symbol":"LTCUSDT", // "hold_volume":"6", // "position_type":1, // "open_type":2, // "frozen_volume":"0", // "close_volume":"0", // "hold_avg_price":"71.681666666666666667", // "close_avg_price":"0", // "open_avg_price":"71.681666666666666667", // "liquidate_price":"0", // "create_time":1701621167225, // "update_time":1701628152614 // } // const marketId = this.safeString(position, 'symbol'); market = this.safeMarket(marketId, market, undefined, 'swap'); const symbol = market['symbol']; const openTimestamp = this.safeInteger(position, 'create_time'); const timestamp = this.safeInteger(position, 'update_time'); const side = this.safeInteger(position, 'position_type'); const marginModeId = this.safeInteger(position, 'open_type'); return this.safePosition({ 'info': position, 'id': undefined, 'symbol': symbol, 'timestamp': openTimestamp, 'datetime': this.iso8601(openTimestamp), 'lastUpdateTimestamp': timestamp, 'hedged': undefined, 'side': (side === 1) ? 'long' : 'short', 'contracts': this.safeNumber(position, 'hold_volume'), 'contractSize': this.safeNumber(market, 'contractSize'), 'entryPrice': this.safeNumber(position, 'open_avg_price'), 'markPrice': this.safeNumber(position, 'hold_avg_price'), 'lastPrice': undefined, 'notional': undefined, 'leverage': undefined, 'collateral': undefined, 'initialMargin': undefined, 'initialMarginPercentage': undefined, 'maintenanceMargin': undefined, 'maintenanceMarginPercentage': undefined, 'unrealizedPnl': undefined, 'realizedPnl': undefined, 'liquidationPrice': this.safeNumber(position, 'liquidate_price'), 'marginMode': (marginModeId === 1) ? 'isolated' : 'cross', 'percentage': undefined, 'marginRatio': undefined, 'stopLossPrice': undefined, 'takeProfitPrice': undefined, }); } handleTrade(client, message) { // // spot // { // "table": "spot/trade", // "data": [ // { // "price": "52700.50", // "s_t": 1630982050, // "side": "buy", // "size": "0.00112", // "symbol": "BTC_USDT" // }, // ] // } // // swap // { // "group":"futures/trade:BTCUSDT", // "data":[ // { // "trade_id":6798697637, // "symbol":"BTCUSDT", // "deal_price":"39735.8", // "deal_vol":"2", // "way":1, // "created_at":"2023-12-03T15:48:23.517518538Z", // "m": true, // } // ] // } // const data = this.safeValue(message, 'data'); if (data === undefined) { return; } let symbol = undefined; const length = data.length; const isSwap = ('group' in message); if (isSwap) { // in swap, chronologically decreasing: 1709536849322, 1709536848954, for (let i = 0; i < length; i++) { const index = length - i - 1; symbol = this.handleTradeLoop(data[index]); } } else { // in spot, chronologically increasing: 1709536771200, 1709536771226, for (let i = 0; i < length; i++) { symbol = this.handleTradeLoop(data[i]); } } client.resolve(this.trades[symbol], 'trade:' + symbol); } handleTradeLoop(entry) { const trade = this.parseWsTrade(entry); const symbol = trade['symbol']; const tradesLimit = this.safeInteger(this.options, 'tradesLimit', 1000); if (this.safeValue(this.trades, symbol) === undefined) { this.trades[symbol] = new ArrayCache(tradesLimit); } const stored = this.trades[symbol]; stored.append(trade); return symbol; } parseWsTrade(trade, market = undefined) { // // spot // { // "ms_t": 1740320841473, // "price": "2806.54", // "s_t": 1740320841, // "side": "sell", // "size": "0.77598", // "symbol": "ETH_USDT" // } // // swap // { // "trade_id": "3000000245258661", // "symbol": "ETHUSDT", // "deal_price": "2811.1", // "deal_vol": "1858", // "way": 2, // "m": true, // "created_at": "2025-02-23T13:59:59.646490751Z" // } // const marketId = this.safeString(trade, 'symbol'); market = this.safeMarket(marketId, market); let timestamp = this.safeInteger(trade, 'ms_t'); let datetime = undefined; if (timestamp === undefined) { datetime = this.safeString(trade, 'created_at'); timestamp = this.parse8601(datetime); } else { datetime = this.iso8601(timestamp); } let takerOrMaker = undefined; // true for public trades let side = this.safeString(trade, 'side'); const buyerMaker = this.safeBool(trade, 'm'); if (buyerMaker !== undefined) { if (side === undefined) { if (buyerMaker) { side = 'sell'; } else { side = 'buy'; } } takerOrMaker = 'taker'; } return this.safeTrade({ 'info': trade, 'id': this.safeString(trade, 'trade_id'), 'order': undefined, 'timestamp': timestamp, 'datetime': datetime, 'symbol': market['symbol'], 'type': undefined, 'side': side, 'price': this.safeString2(trade, 'price', 'deal_price'), 'amount': this.safeString2(trade, 'size', 'deal_vol'), 'cost': undefined, 'takerOrMaker': takerOrMaker, 'fee': undefined, }, market); } handleTicker(client, message) { // // { // "data": [ // { // "base_volume_24h": "78615593.81", // "high_24h": "52756.97", // "last_price": "52638.31", // "low_24h": "50991.35", // "open_24h": "51692.03", // "s_t": 1630981727, // "symbol": "BTC_USDT" // } // ], // "table": "spot/ticker" // } // // { // "data": { // "symbol": "ETHUSDT", // "last_price": "2807.73", // "volume_24": "2227011952", // "range": "0.0273398194664491", // "mark_price": "2807.5", // "index_price": "2808.71047619", // "ask_price": "2808.04", // "ask_vol": "7371", // "bid_price": "2807.28", // "bid_vol": "3561" // }, // "group": "futures/ticker:ETHUSDT@100ms" // } // this.handleBidAsk(client, message); const table = this.safeString(message, 'table'); const isSpot = (table !== undefined); let rawTickers = []; if (isSpot) { rawTickers = this.safeList(message, 'data', []); } else { rawTickers = [this.safeValue(message, 'data', {})]; } if (!rawTickers.length) { return; } for (let i = 0; i < rawTickers.length; i++) { const ticker = isSpot ? this.parseTicker(rawTickers[i]) : this.parseWsSwapTicker(rawTickers[i]); const symbol = ticker['symbol']; this.tickers[symbol] = ticker; const messageHash = 'ticker:' + symbol; client.resolve(ticker, messageHash); } } parseWsSwapTicker(ticker, market = undefined) { // // { // "symbol": "ETHUSDT", // "last_price": "2807.73", // "volume_24": "2227011952", // "range": "0.0273398194664491", // "mark_price": "2807.5", // "index_price": "2808.71047619", // "ask_price": "2808.04", // "ask_vol": "7371", // "bid_price": "2807.28", // "bid_vol": "3561" // } // const marketId = this.safeString(ticker, 'symbol'); return this.safeTicker({ 'symbol': this.safeSymbol(marketId, market, '', 'swap'), 'timestamp': undefined, 'datetime': undefined, 'high': undefined, 'low': undefined, 'bid': this.safeString(ticker, 'bid_price'), 'bidVolume': this.safeString(ticker, 'bid_vol'), 'ask': this.safeString(ticker, 'ask_price'), 'askVolume': this.safeString(ticker, 'ask_vol'), 'vwap': undefined, 'open': undefined, 'close': undefined, 'last': this.safeString(ticker, 'last_price'), 'previousClose': undefined, 'change': undefined, 'percentage': undefined, 'average': undefined, 'baseVolume': undefined, 'quoteVolume': this.safeString(ticker, 'volume_24'), 'info': ticker, 'markPrice': this.safeString(ticker, 'mark_price'), 'indexPrice': this.safeString(ticker, 'index_price'), }, market); } /** * @method * @name bitmart#watchOHLCV * @see https://developer-pro.bitmart.com/en/spot/#public-kline-channel * @see https://developer-pro.bitmart.com/en/futuresv2/#public-klinebin-channel * @description watches 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} [since] timestamp in ms of the earliest candle to fetch * @param {int} [limit] the maximum amount of candles to fetch * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume */ async watchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { await this.loadMarkets(); symbol = this.symbol(symbol); const market = this.market(symbol); let type = 'spot'; [type, params] = this.handleMarketTypeAndParams('watchOrderBook', market, params); const timeframes = this.safeValue(this.options, 'timeframes', {}); const interval = this.safeString(timeframes, timeframe); let name = undefined; if (type === 'spot') { name = 'kline' + interval; } else { name = 'klineBin' + interval; } const ohlcv = await this.subscribe(name, symbol, type, params); if (this.newUpdates) { limit = ohlcv.getLimit(symbol, limit); } return this.filterBySinceLimit(ohlcv, since, limit, 0, true); } handleOHLCV(client, message) { // // { // "data": [ // { // "candle": [ // 1631056350, // "46532.83", // "46555.71", // "46511.41", // "46555.71", // "0.25" // ], // "symbol": "BTC_USDT" // } // ], // "table": "spot/kline1m" // } // swap // { // "group":"futures/klineBin1m:BTCUSDT", // "data":{ //