astro-perp-ccxt-dev
Version:
2,570 lines • 121 kB
JavaScript
'use strict';
var blofin$1 = require('./abstract/blofin.js');
var errors = require('./base/errors.js');
var Precise = require('./base/Precise.js');
var number = require('./base/functions/number.js');
var sha256 = require('./static_dependencies/noble-hashes/sha256.js');
// ----------------------------------------------------------------------------
// ---------------------------------------------------------------------------
/**
* @class blofin
* @augments Exchange
*/
class blofin extends blofin$1 {
describe() {
return this.deepExtend(super.describe(), {
'id': 'blofin',
'name': 'BloFin',
'countries': ['US'],
'version': 'v1',
'rateLimit': 100,
'pro': true,
'has': {
'CORS': undefined,
'spot': false,
'margin': false,
'swap': true,
'future': false,
'option': false,
'addMargin': false,
'borrowMargin': false,
'cancelAllOrders': false,
'cancelOrder': true,
'cancelOrders': true,
'closeAllPositions': false,
'closePosition': true,
'createDepositAddress': false,
'createMarketBuyOrderWithCost': false,
'createMarketSellOrderWithCost': false,
'createOrder': true,
'createOrders': true,
'createOrderWithTakeProfitAndStopLoss': true,
'createPostOnlyOrder': false,
'createReduceOnlyOrder': false,
'createStopLimitOrder': false,
'createStopLossOrder': true,
'createStopMarketOrder': false,
'createStopOrder': false,
'createTakeProfitOrder': true,
'createTriggerOrder': true,
'editOrder': false,
'fetchAccounts': false,
'fetchBalance': true,
'fetchBidsAsks': undefined,
'fetchBorrowInterest': false,
'fetchBorrowRateHistories': false,
'fetchBorrowRateHistory': false,
'fetchCanceledOrders': false,
'fetchClosedOrder': false,
'fetchClosedOrders': true,
'fetchCrossBorrowRate': false,
'fetchCrossBorrowRates': false,
'fetchCurrencies': false,
'fetchDeposit': false,
'fetchDepositAddress': false,
'fetchDepositAddresses': false,
'fetchDepositAddressesByNetwork': false,
'fetchDeposits': true,
'fetchDepositsWithdrawals': false,
'fetchDepositWithdrawFee': 'emulated',
'fetchDepositWithdrawFees': false,
'fetchFundingHistory': true,
'fetchFundingRate': true,
'fetchFundingRateHistory': true,
'fetchFundingRates': false,
'fetchGreeks': false,
'fetchIndexOHLCV': false,
'fetchIsolatedBorrowRate': false,
'fetchIsolatedBorrowRates': false,
'fetchL3OrderBook': false,
'fetchLedger': true,
'fetchLedgerEntry': undefined,
'fetchLeverage': true,
'fetchLeverages': true,
'fetchLeverageTiers': false,
'fetchMarginMode': true,
'fetchMarginModes': false,
'fetchMarketLeverageTiers': false,
'fetchMarkets': true,
'fetchMarkOHLCV': false,
'fetchMySettlementHistory': false,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenInterest': false,
'fetchOpenInterestHistory': false,
'fetchOpenOrder': undefined,
'fetchOpenOrders': true,
'fetchOrder': undefined,
'fetchOrderBook': true,
'fetchOrderBooks': false,
'fetchOrders': false,
'fetchOrderTrades': true,
'fetchPosition': true,
'fetchPositionMode': true,
'fetchPositions': true,
'fetchPositionsForSymbol': false,
'fetchPositionsRisk': false,
'fetchPremiumIndexOHLCV': false,
'fetchSettlementHistory': false,
'fetchStatus': false,
'fetchTicker': true,
'fetchTickers': true,
'fetchTime': false,
'fetchTrades': true,
'fetchTradingFee': false,
'fetchTradingFees': false,
'fetchTradingLimits': false,
'fetchTransactionFee': false,
'fetchTransactionFees': false,
'fetchTransactions': false,
'fetchTransfer': false,
'fetchTransfers': false,
'fetchUnderlyingAssets': false,
'fetchVolatilityHistory': false,
'fetchWithdrawal': false,
'fetchWithdrawals': true,
'fetchWithdrawalWhitelist': false,
'reduceMargin': false,
'repayCrossMargin': false,
'setLeverage': true,
'setMargin': false,
'setMarginMode': true,
'setPositionMode': true,
'signIn': false,
'transfer': true,
'withdraw': false,
},
'timeframes': {
'1m': '1m',
'3m': '3m',
'5m': '5m',
'15m': '15m',
'30m': '30m',
'1h': '1H',
'2h': '2H',
'4h': '4H',
'6h': '6H',
'8h': '8H',
'12h': '12H',
'1d': '1D',
'3d': '3D',
'1w': '1W',
'1M': '1M',
},
'hostname': 'www.blofin.com',
'urls': {
'logo': 'https://github.com/user-attachments/assets/518cdf80-f05d-4821-a3e3-d48ceb41d73b',
'api': {
'rest': 'https://openapi.blofin.com',
},
'test': {
'rest': 'https://demo-trading-openapi.blofin.com',
},
'referral': {
'url': 'https://blofin.com/register?referral_code=f79EsS',
'discount': 0.05,
},
'www': 'https://www.blofin.com',
'doc': 'https://blofin.com/docs',
},
'api': {
'public': {
'get': {
'market/instruments': 1,
'market/tickers': 1,
'market/books': 1,
'market/trades': 1,
'market/candles': 1,
'market/mark-price': 1,
'market/funding-rate': 1,
'market/funding-rate-history': 1,
},
},
'private': {
'get': {
'asset/balances': 1,
'trade/orders-pending': 1,
'trade/fills-history': 1,
'asset/deposit-history': 1,
'asset/withdrawal-history': 1,
'asset/bills': 1,
'account/balance': 1,
'account/positions': 1,
'account/leverage-info': 1,
'account/margin-mode': 1,
'account/position-mode': 1,
'account/batch-leverage-info': 1,
'trade/orders-tpsl-pending': 1,
'trade/orders-algo-pending': 1,
'trade/orders-history': 1,
'trade/orders-tpsl-history': 1,
'trade/orders-algo-history': 1,
'trade/order/price-range': 1,
'user/query-apikey': 1,
'affiliate/basic': 1,
'copytrading/instruments': 1,
'copytrading/account/balance': 1,
'copytrading/account/positions-by-order': 1,
'copytrading/account/positions-details-by-order': 1,
'copytrading/account/positions-by-contract': 1,
'copytrading/account/position-mode': 1,
'copytrading/account/leverage-info': 1,
'copytrading/trade/orders-pending': 1,
'copytrading/trade/pending-tpsl-by-contract': 1,
'copytrading/trade/position-history-by-order': 1,
'copytrading/trade/orders-history': 1,
'copytrading/trade/pending-tpsl-by-order': 1,
},
'post': {
'account/set-margin-mode': 1,
'account/set-position-mode': 1,
'trade/order': 1,
'trade/order-algo': 1,
'trade/cancel-order': 1,
'trade/cancel-algo': 1,
'account/set-leverage': 1,
'trade/batch-orders': 1,
'trade/order-tpsl': 1,
'trade/cancel-batch-orders': 1,
'trade/cancel-tpsl': 1,
'trade/close-position': 1,
'asset/transfer': 1,
'copytrading/account/set-position-mode': 1,
'copytrading/account/set-leverage': 1,
'copytrading/trade/place-order': 1,
'copytrading/trade/cancel-order': 1,
'copytrading/trade/place-tpsl-by-contract': 1,
'copytrading/trade/cancel-tpsl-by-contract': 1,
'copytrading/trade/place-tpsl-by-order': 1,
'copytrading/trade/cancel-tpsl-by-order': 1,
'copytrading/trade/close-position-by-order': 1,
'copytrading/trade/close-position-by-contract': 1,
},
},
},
'fees': {
'swap': {
'taker': this.parseNumber('0.00060'),
'maker': this.parseNumber('0.00020'),
},
},
'requiredCredentials': {
'apiKey': true,
'secret': true,
'password': true,
},
'features': {
'default': {
'sandbox': false,
'createOrder': {
'timeInForce': {
'IOC': true,
'FOK': true,
'PO': true,
'GTD': false,
},
'leverage': false,
'marketBuyRequiresPrice': false,
'marketBuyByCost': false,
'selfTradePrevention': false,
'trailing': false,
'iceberg': false,
},
'createOrders': {
'max': 10,
},
'fetchMyTrades': {
'marginMode': false,
'limit': 100,
'daysBack': 100000,
'untilDays': 100000,
'symbolRequired': false,
},
'fetchOrder': undefined,
'fetchOpenOrders': {
'marginMode': false,
'limit': 100,
'trigger': true,
'trailing': false,
'symbolRequired': false,
},
'fetchOrders': undefined,
'fetchClosedOrders': {
'marginMode': false,
'limit': 1000,
'daysBack': 100000,
'daysBackCanceled': 1,
'untilDays': 100000,
'trigger': true,
'trailing': false,
'symbolRequired': false,
},
'fetchOHLCV': {
'limit': 1440,
},
},
'spot': {
'extends': 'default',
'createOrder': {
'marginMode': false,
'triggerPrice': false,
'triggerPriceType': undefined,
'triggerDirection': false,
'stopLossPrice': false,
'takeProfitPrice': false,
'attachedStopLossTakeProfit': undefined,
'hedged': false,
},
},
'forDerivatives': {
'extends': 'default',
'createOrder': {
'marginMode': true,
'triggerPrice': false,
'triggerPriceType': undefined,
'triggerDirection': false,
'stopLossPrice': true,
'takeProfitPrice': true,
'attachedStopLossTakeProfit': {
'triggerPriceType': undefined,
'price': true,
},
'hedged': true,
},
},
'swap': {
'linear': {
'extends': 'forDerivatives',
},
'inverse': undefined,
},
'future': {
'linear': undefined,
'inverse': undefined,
},
},
'exceptions': {
'exact': {
'400': errors.BadRequest,
'401': errors.AuthenticationError,
'500': errors.ExchangeError,
'404': errors.BadRequest,
'405': errors.BadRequest,
'406': errors.BadRequest,
'429': errors.RateLimitExceeded,
'152001': errors.BadRequest,
'152002': errors.BadRequest,
'152003': errors.BadRequest,
'152004': errors.BadRequest,
'152005': errors.BadRequest,
'152006': errors.InvalidOrder,
'152007': errors.InvalidOrder,
'152008': errors.InvalidOrder,
'152009': errors.InvalidOrder,
'150003': errors.InvalidOrder,
'150004': errors.InvalidOrder,
'542': errors.InvalidOrder,
'102002': errors.InvalidOrder,
'102005': errors.InvalidOrder,
'102014': errors.InvalidOrder,
'102015': errors.InvalidOrder,
'102022': errors.InvalidOrder,
'102037': errors.InvalidOrder,
'102038': errors.InvalidOrder,
'102039': errors.InvalidOrder,
'102040': errors.InvalidOrder,
'102047': errors.InvalidOrder,
'102048': errors.InvalidOrder,
'102049': errors.InvalidOrder,
'102050': errors.InvalidOrder,
'102051': errors.InvalidOrder,
'102052': errors.InvalidOrder,
'102053': errors.InvalidOrder,
'102054': errors.InvalidOrder,
'102055': errors.InvalidOrder,
'102064': errors.BadRequest,
'102065': errors.BadRequest,
'102068': errors.BadRequest,
'103013': errors.ExchangeError,
'Order failed. Insufficient USDT margin in account': errors.InsufficientFunds, // Insufficient USDT margin in account
},
'broad': {
'Internal Server Error': errors.ExchangeNotAvailable,
'server error': errors.ExchangeNotAvailable, // {"code":500,"data":{},"detailMsg":"","error_code":"500","error_message":"server error 1236805249","msg":"server error 1236805249"}
},
},
'httpExceptions': {
'429': errors.ExchangeNotAvailable, // https://github.com/ccxt/ccxt/issues/9612
},
'precisionMode': number.TICK_SIZE,
'options': {
'brokerId': 'ec6dd3a7dd982d0b',
'accountsByType': {
'swap': 'futures',
'funding': 'funding',
'future': 'futures',
'copy_trading': 'copy_trading',
'earn': 'earn',
'spot': 'spot',
},
'accountsById': {
'funding': 'funding',
'futures': 'swap',
'copy_trading': 'copy_trading',
'earn': 'earn',
'spot': 'spot',
},
'defaultNetwork': 'ERC20',
'defaultNetworks': {
'ETH': 'ERC20',
'BTC': 'BTC',
'USDT': 'TRC20',
},
'networks': {
'BTC': 'Bitcoin',
'BEP20': 'BSC',
'ERC20': 'ERC20',
'TRC20': 'TRC20',
},
'fetchOpenInterestHistory': {
'timeframes': {
'5m': '5m',
'1h': '1H',
'8h': '8H',
'1d': '1D',
'5M': '5m',
'1H': '1H',
'8H': '8H',
'1D': '1D',
},
},
'fetchOHLCV': {
// 'type': 'Candles', // Candles or HistoryCandles, IndexCandles, MarkPriceCandles
'timezone': 'UTC', // UTC, HK
},
'fetchPositions': {
'method': 'privateGetAccountPositions', // privateGetAccountPositions or privateGetAccountPositionsHistory
},
'createOrder': 'privatePostTradeOrder',
'createMarketBuyOrderRequiresPrice': false,
'fetchMarkets': ['swap'],
'defaultType': 'swap',
'fetchLedger': {
'method': 'privateGetAssetBills',
},
'fetchOpenOrders': {
'method': 'privateGetTradeOrdersPending',
},
'cancelOrders': {
'method': 'privatePostTradeCancelBatchOrders',
},
'fetchCanceledOrders': {
'method': 'privateGetTradeOrdersHistory', // privateGetTradeOrdersTpslHistory
},
'fetchClosedOrders': {
'method': 'privateGetTradeOrdersHistory', // privateGetTradeOrdersTpslHistory
},
'withdraw': {
// a funding password credential is required by the exchange for the
// withdraw call (not to be confused with the api password credential)
'password': undefined,
'pwd': undefined, // password or pwd both work
},
'exchangeType': {
'spot': 'SPOT',
'swap': 'SWAP',
'SPOT': 'SPOT',
'SWAP': 'SWAP',
},
},
});
}
/**
* @method
* @name blofin#fetchMarkets
* @description retrieves data on all markets for blofin
* @see https://blofin.com/docs#get-instruments
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object[]} an array of objects representing market data
*/
async fetchMarkets(params = {}) {
const response = await this.publicGetMarketInstruments(params);
const data = this.safeList(response, 'data', []);
return this.parseMarkets(data);
}
parseMarket(market) {
const id = this.safeString(market, 'instId');
const type = this.safeStringLower(market, 'instType');
const spot = (type === 'spot');
const future = (type === 'future');
const swap = (type === 'swap');
const option = (type === 'option');
const contract = swap || future;
const baseId = this.safeString(market, 'baseCurrency');
const quoteId = this.safeString(market, 'quoteCurrency');
const settleId = this.safeString(market, 'quoteCurrency');
const settle = this.safeCurrencyCode(settleId);
const base = this.safeCurrencyCode(baseId);
const quote = this.safeCurrencyCode(quoteId);
let symbol = base + '/' + quote;
if (swap) {
symbol = symbol + ':' + settle;
}
const expiry = undefined;
const strikePrice = undefined;
const optionType = undefined;
const tickSize = this.safeString(market, 'tickSize');
const fees = this.safeDict2(this.fees, type, 'trading', {});
const taker = this.safeNumber(fees, 'taker');
const maker = this.safeNumber(fees, 'maker');
let maxLeverage = this.safeString(market, 'maxLeverage', '100');
maxLeverage = Precise["default"].stringMax(maxLeverage, '1');
const isActive = (this.safeString(market, 'state') === 'live');
return this.safeMarketStructure({
'id': id,
'symbol': symbol,
'base': base,
'quote': quote,
'baseId': baseId,
'quoteId': quoteId,
'settle': settle,
'settleId': settleId,
'type': type,
'spot': spot,
'option': option,
'margin': spot && (Precise["default"].stringGt(maxLeverage, '1')),
'swap': swap,
'future': future,
'active': isActive,
'taker': taker,
'maker': maker,
'contract': contract,
'linear': contract ? (quoteId === settleId) : undefined,
'inverse': contract ? (baseId === settleId) : undefined,
'contractSize': contract ? this.safeNumber(market, 'contractValue') : undefined,
'expiry': expiry,
'expiryDatetime': expiry,
'strike': strikePrice,
'optionType': optionType,
'created': this.safeInteger(market, 'listTime'),
'precision': {
'amount': this.safeNumber(market, 'lotSize'),
'price': this.parseNumber(tickSize),
},
'limits': {
'leverage': {
'min': this.parseNumber('1'),
'max': this.parseNumber(maxLeverage),
},
'amount': {
'min': this.safeNumber(market, 'minSize'),
'max': undefined,
},
'price': {
'min': undefined,
'max': undefined,
},
'cost': {
'min': undefined,
'max': undefined,
},
},
'info': market,
});
}
/**
* @method
* @name blofin#fetchOrderBook
* @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
* @see https://blofin.com/docs#get-order-book
* @param {string} symbol unified symbol of the market to fetch the order book for
* @param {int} [limit] the maximum amount of order book entries to return
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
*/
async fetchOrderBook(symbol, limit = undefined, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const request = {
'instId': market['id'],
};
limit = (limit === undefined) ? 50 : limit;
if (limit !== undefined) {
request['size'] = limit; // max 100
}
const response = await this.publicGetMarketBooks(this.extend(request, params));
//
// {
// "code": "0",
// "msg": "",
// "data": [
// {
// "asks": [
// ["0.07228","4.211619","0","2"], // price, amount, liquidated orders, total open orders
// ["0.0723","299.880364","0","2"],
// ["0.07231","3.72832","0","1"],
// ],
// "bids": [
// ["0.07221","18.5","0","1"],
// ["0.0722","18.5","0","1"],
// ["0.07219","0.505407","0","1"],
// ],
// "ts": "1621438475342"
// }
// ]
// }
//
const data = this.safeList(response, 'data', []);
const first = this.safeDict(data, 0, {});
const timestamp = this.safeInteger(first, 'ts');
return this.parseOrderBook(first, symbol, timestamp);
}
parseTicker(ticker, market = undefined) {
//
// response similar for REST & WS
//
// {
// instId: "ADA-USDT",
// ts: "1707736811486",
// last: "0.5315",
// lastSize: "4",
// askPrice: "0.5318",
// askSize: "248",
// bidPrice: "0.5315",
// bidSize: "63",
// open24h: "0.5555",
// high24h: "0.5563",
// low24h: "0.5315",
// volCurrency24h: "198560100",
// vol24h: "1985601",
// }
//
const timestamp = this.safeInteger(ticker, 'ts');
const marketId = this.safeString(ticker, 'instId');
market = this.safeMarket(marketId, market, '-');
const symbol = market['symbol'];
const last = this.safeString(ticker, 'last');
const open = this.safeString(ticker, 'open24h');
const spot = this.safeBool(market, 'spot', false);
const quoteVolume = spot ? this.safeString(ticker, 'volCurrency24h') : undefined;
const baseVolume = this.safeString(ticker, 'vol24h');
const high = this.safeString(ticker, 'high24h');
const low = this.safeString(ticker, 'low24h');
return this.safeTicker({
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'high': high,
'low': low,
'bid': this.safeString(ticker, 'bidPrice'),
'bidVolume': this.safeString(ticker, 'bidSize'),
'ask': this.safeString(ticker, 'askPrice'),
'askVolume': this.safeString(ticker, 'askSize'),
'vwap': undefined,
'open': open,
'close': last,
'last': last,
'previousClose': undefined,
'change': undefined,
'percentage': undefined,
'average': undefined,
'baseVolume': baseVolume,
'quoteVolume': quoteVolume,
'indexPrice': this.safeString(ticker, 'indexPrice'),
'markPrice': this.safeString(ticker, 'markPrice'),
'info': ticker,
}, market);
}
/**
* @method
* @name blofin#fetchTicker
* @description fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
* @see https://blofin.com/docs#get-tickers
* @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 fetchTicker(symbol, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const request = {
'instId': market['id'],
};
const response = await this.publicGetMarketTickers(this.extend(request, params));
const data = this.safeList(response, 'data', []);
const first = this.safeDict(data, 0, {});
return this.parseTicker(first, market);
}
/**
* @method
* @name blofin#fetchMarkPrice
* @description fetches mark price for the market
* @see https://docs.blofin.com/index.html#get-mark-price
* @param {string} symbol unified market symbol
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.subType] "linear" or "inverse"
* @returns {object} a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=ticker-structure}
*/
async fetchMarkPrice(symbol, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const request = {
'symbol': market['id'],
};
const response = await this.publicGetMarketMarkPrice(this.extend(request, params));
const data = this.safeList(response, 'data', []);
const first = this.safeDict(data, 0, {});
return this.parseTicker(first, market);
}
/**
* @method
* @name blofin#fetchTickers
* @description fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
* @see https://blofin.com/docs#get-tickers
* @param {string[]} [symbols] unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=ticker-structure}
*/
async fetchTickers(symbols = undefined, params = {}) {
await this.loadMarkets();
symbols = this.marketSymbols(symbols);
const response = await this.publicGetMarketTickers(params);
const tickers = this.safeList(response, 'data', []);
return this.parseTickers(tickers, symbols);
}
parseTrade(trade, market = undefined) {
//
// fetch trades (response similar for REST & WS)
//
// {
// "tradeId": "3263934920",
// "instId": "LTC-USDT",
// "price": "67.87",
// "size": "1",
// "side": "buy",
// "ts": "1707232020854"
// }
//
// my trades
// {
// "instId": "LTC-USDT",
// "tradeId": "1440847",
// "orderId": "2075705202",
// "fillPrice": "67.850000000000000000",
// "fillSize": "1.000000000000000000",
// "fillPnl": "0.000000000000000000",
// "side": "buy",
// "positionSide": "net",
// "fee": "0.040710000000000000",
// "ts": "1707224678878",
// "brokerId": ""
// }
//
const id = this.safeString(trade, 'tradeId');
const marketId = this.safeString(trade, 'instId');
market = this.safeMarket(marketId, market, '-');
const symbol = market['symbol'];
const timestamp = this.safeInteger(trade, 'ts');
const price = this.safeString2(trade, 'price', 'fillPrice');
const amount = this.safeString2(trade, 'size', 'fillSize');
const side = this.safeString(trade, 'side');
const orderId = this.safeString(trade, 'orderId');
const feeCost = this.safeString(trade, 'fee');
let fee = undefined;
if (feeCost !== undefined) {
fee = {
'cost': feeCost,
'currency': market['settle'],
};
}
return this.safeTrade({
'info': trade,
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'symbol': symbol,
'id': id,
'order': orderId,
'type': undefined,
'takerOrMaker': undefined,
'side': side,
'price': price,
'amount': amount,
'cost': undefined,
'fee': fee,
}, market);
}
/**
* @method
* @name blofin#fetchTrades
* @description get the list of most recent trades for a particular symbol
* @see https://blofin.com/docs#get-trades
* @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
* @param {boolean} [params.paginate] *only applies to publicGetMarketHistoryTrades* default false, when true will automatically paginate by calling this endpoint multiple times
* @returns {Trade[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades}
*/
async fetchTrades(symbol, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
let paginate = false;
[paginate, params] = this.handleOptionAndParams(params, 'fetchTrades', 'paginate');
if (paginate) {
return await this.fetchPaginatedCallCursor('fetchTrades', symbol, since, limit, params, 'tradeId', 'after', undefined, 100);
}
const market = this.market(symbol);
const request = {
'instId': market['id'],
};
let response = undefined;
if (limit !== undefined) {
request['limit'] = limit; // default 100
}
let method = undefined;
[method, params] = this.handleOptionAndParams(params, 'fetchTrades', 'method', 'publicGetMarketTrades');
if (method === 'publicGetMarketTrades') {
response = await this.publicGetMarketTrades(this.extend(request, params));
}
const data = this.safeList(response, 'data', []);
return this.parseTrades(data, market, since, limit);
}
parseOHLCV(ohlcv, market = undefined) {
//
// [
// "1678928760000", // timestamp
// "24341.4", // open
// "24344", // high
// "24313.2", // low
// "24323", // close
// "628", // contract volume
// "2.5819", // base volume
// "62800", // quote volume
// "0" // candlestick state
// ]
//
return [
this.safeInteger(ohlcv, 0),
this.safeNumber(ohlcv, 1),
this.safeNumber(ohlcv, 2),
this.safeNumber(ohlcv, 3),
this.safeNumber(ohlcv, 4),
this.safeNumber(ohlcv, 6),
];
}
/**
* @method
* @name blofin#fetchOHLCV
* @description fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
* @see https://blofin.com/docs#get-candlesticks
* @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
* @param {int} [params.until] timestamp in ms of the latest candle to fetch
* @param {boolean} [params.paginate] default false, when true will automatically paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
* @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume
*/
async fetchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
let paginate = false;
[paginate, params] = this.handleOptionAndParams(params, 'fetchOHLCV', 'paginate');
if (paginate) {
return await this.fetchPaginatedCallDeterministic('fetchOHLCV', symbol, since, limit, timeframe, params, 100);
}
if (limit === undefined) {
limit = 100; // default 100, max 100
}
const request = {
'instId': market['id'],
'bar': this.safeString(this.timeframes, timeframe, timeframe),
'limit': limit,
};
const until = this.safeInteger(params, 'until');
if (until !== undefined) {
request['after'] = until;
params = this.omit(params, 'until');
}
let response = undefined;
response = await this.publicGetMarketCandles(this.extend(request, params));
const data = this.safeList(response, 'data', []);
return this.parseOHLCVs(data, market, timeframe, since, limit);
}
/**
* @method
* @name blofin#fetchFundingRateHistory
* @description fetches historical funding rate prices
* @see https://blofin.com/docs#get-funding-rate-history
* @param {string} symbol unified symbol of the market to fetch the funding rate history for
* @param {int} [since] timestamp in ms of the earliest funding rate to fetch
* @param {int} [limit] the maximum amount of [funding rate structures]{@link https://docs.ccxt.com/#/?id=funding-rate-history-structure} to fetch
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {boolean} [params.paginate] default false, when true will automatically paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
* @returns {object[]} a list of [funding rate structures]{@link https://docs.ccxt.com/#/?id=funding-rate-history-structure}
*/
async fetchFundingRateHistory(symbol = undefined, since = undefined, limit = undefined, params = {}) {
if (symbol === undefined) {
throw new errors.ArgumentsRequired(this.id + ' fetchFundingRateHistory() requires a symbol argument');
}
await this.loadMarkets();
let paginate = false;
[paginate, params] = this.handleOptionAndParams(params, 'fetchFundingRateHistory', 'paginate');
if (paginate) {
return await this.fetchPaginatedCallDeterministic('fetchFundingRateHistory', symbol, since, limit, '8h', params);
}
const market = this.market(symbol);
const request = {
'instId': market['id'],
};
if (since !== undefined) {
request['before'] = Math.max(since - 1, 0);
}
if (limit !== undefined) {
request['limit'] = limit;
}
const response = await this.publicGetMarketFundingRateHistory(this.extend(request, params));
const rates = [];
const data = this.safeList(response, 'data', []);
for (let i = 0; i < data.length; i++) {
const rate = data[i];
const timestamp = this.safeInteger(rate, 'fundingTime');
rates.push({
'info': rate,
'symbol': market['symbol'],
'fundingRate': this.safeNumber(rate, 'fundingRate'),
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
});
}
const sorted = this.sortBy(rates, 'timestamp');
return this.filterBySymbolSinceLimit(sorted, market['symbol'], since, limit);
}
parseFundingRate(contract, market = undefined) {
//
// {
// "fundingRate": "0.00027815",
// "fundingTime": "1634256000000",
// "instId": "BTC-USD-SWAP",
// }
//
const marketId = this.safeString(contract, 'instId');
const symbol = this.safeSymbol(marketId, market);
const fundingTime = this.safeInteger(contract, 'fundingTime');
// > The current interest is 0.
return {
'info': contract,
'symbol': symbol,
'markPrice': undefined,
'indexPrice': undefined,
'interestRate': this.parseNumber('0'),
'estimatedSettlePrice': undefined,
'timestamp': undefined,
'datetime': undefined,
'fundingRate': this.safeNumber(contract, 'fundingRate'),
'fundingTimestamp': fundingTime,
'fundingDatetime': this.iso8601(fundingTime),
'nextFundingRate': undefined,
'nextFundingTimestamp': undefined,
'nextFundingDatetime': undefined,
'previousFundingRate': undefined,
'previousFundingTimestamp': undefined,
'previousFundingDatetime': undefined,
'interval': undefined,
};
}
/**
* @method
* @name blofin#fetchFundingRate
* @description fetch the current funding rate
* @see https://blofin.com/docs#get-funding-rate
* @param {string} symbol unified market symbol
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a [funding rate structure]{@link https://docs.ccxt.com/#/?id=funding-rate-structure}
*/
async fetchFundingRate(symbol, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
if (!market['swap']) {
throw new errors.ExchangeError(this.id + ' fetchFundingRate() is only valid for swap markets');
}
const request = {
'instId': market['id'],
};
const response = await this.publicGetMarketFundingRate(this.extend(request, params));
//
// {
// "code": "0",
// "data": [
// {
// "fundingRate": "0.00027815",
// "fundingTime": "1634256000000",
// "instId": "BTC-USD-SWAP",
// }
// ],
// "msg": ""
// }
//
const data = this.safeList(response, 'data', []);
const entry = this.safeDict(data, 0, {});
return this.parseFundingRate(entry, market);
}
parseBalanceByType(response) {
const data = this.safeList(response, 'data');
if ((data !== undefined) && Array.isArray(data)) {
return this.parseFundingBalance(response);
}
else {
return this.parseBalance(response);
}
}
parseBalance(response) {
//
// "data" similar for REST & WS
//
// {
// "code": "0",
// "msg": "success",
// "data": {
// "ts": "1697021343571",
// "totalEquity": "10011254.077985990315787910",
// "isolatedEquity": "861.763132108800000000",
// "details": [
// {
// "currency": "USDT",
// "equity": "10014042.988958415234430699548",
// "balance": "10013119.885958415234430699",
// "ts": "1697021343571",
// "isolatedEquity": "862.003200000000000000048",
// "available": "9996399.4708691159703362725",
// "availableEquity": "9996399.4708691159703362725",
// "frozen": "15805.149672632597427761",
// "orderFrozen": "14920.994472632597427761",
// "equityUsd": "10011254.077985990315787910",
// "isolatedUnrealizedPnl": "-22.151999999999999999952",
// "bonus": "0" // present only in REST
// "unrealizedPnl": "0" // present only in WS
// }
// ]
// }
// }
//
const result = { 'info': response };
const data = this.safeDict(response, 'data', {});
const timestamp = this.safeInteger(data, 'ts');
const details = this.safeList(data, 'details', []);
for (let i = 0; i < details.length; i++) {
const balance = details[i];
const currencyId = this.safeString(balance, 'currency');
const code = this.safeCurrencyCode(currencyId);
const account = this.account();
// it may be incorrect to use total, free and used for swap accounts
const eq = this.safeString(balance, 'equity');
const availEq = this.safeString(balance, 'available');
if ((eq === undefined) || (availEq === undefined)) {
account['free'] = this.safeString(balance, 'availableEquity');
account['used'] = this.safeString(balance, 'frozen');
}
else {
account['total'] = eq;
account['free'] = availEq;
}
result[code] = account;
}
result['timestamp'] = timestamp;
result['datetime'] = this.iso8601(timestamp);
return this.safeBalance(result);
}
parseFundingBalance(response) {
//
// {
// "code": "0",
// "msg": "success",
// "data": [
// {
// "currency": "USDT",
// "balance": "10012514.919418081548717298",
// "available": "9872132.414278782284622898",
// "frozen": "138556.471805965930761067",
// "bonus": "0"
// }
// ]
// }
//
const result = { 'info': response };
const data = this.safeList(response, 'data', []);
for (let i = 0; i < data.length; i++) {
const balance = data[i];
const currencyId = this.safeString(balance, 'currency');
const code = this.safeCurrencyCode(currencyId);
const account = this.account();
// it may be incorrect to use total, free and used for swap accounts
account['total'] = this.safeString(balance, 'balance');
account['free'] = this.safeString(balance, 'available');
account['used'] = this.safeString(balance, 'frozen');
result[code] = account;
}
return this.safeBalance(result);
}
parseTradingFee(fee, market = undefined) {
return {
'info': fee,
'symbol': this.safeSymbol(undefined, market),
// blofin returns the fees as negative values opposed to other exchanges, so the sign needs to be flipped
'maker': this.parseNumber(Precise["default"].stringNeg(this.safeString2(fee, 'maker', 'makerU'))),
'taker': this.parseNumber(Precise["default"].stringNeg(this.safeString2(fee, 'taker', 'takerU'))),
'percentage': undefined,
'tierBased': undefined,
};
}
/**
* @method
* @name blofin#fetchBalance
* @description query for balance and get the amount of funds available for trading or funds locked in orders
* @see https://blofin.com/docs#get-balance
* @see https://blofin.com/docs#get-futures-account-balance
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.accountType] the type of account to fetch the balance for, either 'funding' or 'futures' or 'copy_trading' or 'earn'
* @returns {object} a [balance structure]{@link https://docs.ccxt.com/#/?id=balance-structure}
*/
async fetchBalance(params = {}) {
await this.loadMarkets();
let accountType = undefined;
[accountType, params] = this.handleOptionAndParams2(params, 'fetchBalance', 'accountType', 'type');
const request = {};
let response = undefined;
if (accountType !== undefined && accountType !== 'swap') {
const options = this.safeDict(this.options, 'accountsByType', {});
const parsedAccountType = this.safeString(options, accountType, accountType);
request['accountType'] = parsedAccountType;
response = await this.privateGetAssetBalances(this.extend(request, params));
}
else {
response = await this.privateGetAccountBalance(this.extend(request, params));
}
return this.parseBalanceByType(response);
}
createOrderRequest(symbol, type, side, amount, price = undefined, params = {}) {
const market = this.market(symbol);
const request = {
'instId': market['id'],
'side': side,
'orderType': type,
'size': this.amountToPrecision(symbol, amount),
'brokerId': this.safeString(this.options, 'brokerId', 'ec6dd3a7dd982d0b'),
};
let marginMode = undefined;
[marginMode, params] = this.handleMarginModeAndParams('createOrder', params, 'cross');
request['marginMode'] = marginMode;
const triggerPrice = this.safeString(params, 'triggerPrice');
const timeInForce = this.safeString(params, 'timeInForce', 'GTC');
const isHedged = this.safeBool(params, 'hedged', false);
if (isHedged) {
request['positionSide'] = (side === 'buy') ? 'long' : 'short';
}
const isMarketOrder = type === 'market';
params = this.omit(params, ['timeInForce']);
const ioc = (timeInForce === 'IOC') || (type === 'ioc');
const marketIOC = (isMarketOrder && ioc);
if (isMarketOrder || marketIOC) {
request['orderType'] = 'market';
}
else {
const key = (triggerPrice !== undefined) ? 'orderPrice' : 'price';
request[key] = this.priceToPrecision(symbol, price);
}
let postOnly = false;
[postOnly, params] = this.handlePostOnly(isMarketOrder, type === 'post_only', params);
if (postOnly) {
request['type'] = 'post_only';
}
const stopLoss = this.safeDict(params, 'stopLoss');
const takeProfit = this.safeDict(params, 'takeProfit');
params = this.omit(params, ['stopLoss', 'takeProfit', 'hedged']);
const isStopLoss = stopLoss !== undefined;
const isTakeProfit = takeProfit !== undefined;
if (isStopLoss || isTakeProfit) {
if (isStopLoss) {
const slTriggerPrice = this.safeString2(stopLoss, 'triggerPrice', 'stopPrice');
request['slTriggerPrice'] = this.priceToPrecision(symbol, slTriggerPrice);
const slOrderPrice = this.safeString(stopLoss, 'price', '-1');
request['slOrderPrice'] = this.priceToPrecision(symbol, slOrderPrice);
}
if (isTakeProfit) {
const tpTriggerPrice = this.safeString2(takeProfit, 'triggerPrice', 'stopPrice');
request['tpTriggerPrice'] = this.priceToPrecision(symbol, tpTriggerPrice);
const tpPrice = this.safeString(takeProfit, 'price', '-1');
request['tpOrderPrice'] = this.priceToPrecision(symbol, tpPrice);
}
}
else if (triggerPrice !== undefined) {
request['orderType'] = 'trigger';
request['triggerPrice'] = this.priceToPrecision(symbol, triggerPrice);
if (isMarketOrder) {
request['orderPrice'] = '-1';
}
}
return this.extend(request, params);
}
parseOrderStatus(status) {
const statuses = {
'canceled': 'canceled',
'order_failed': 'canceled',
'live': 'open',
'partially_filled': 'open',
'filled': 'closed',
'effective': 'closed',
};
return this.safeString(statuses, status, status);
}
parseOrder(order, market = undefined) {
//
// response similar for REST & WS
//
// {
// "orderId": "2075628533",
// "clientOrderId": "",
// "instId": "LTC-USDT",
// "marginMode": "cross",
// "positionSide": "net",
// "side": "buy",
// "orderType": "market",
// "price": "0.000000000000000000",
// "size": "1.000000000000000000",
// "reduceOnly": "true",
// "leverage": "3",
// "state": "filled",
// "filledSize": "1.000000000000000000",
// "pnl": "-0.050000000000000000",
// "averagePrice": "68.110000000000000000",
// "fee": "0.040866000000000000",
// "createTime": "1706891359010",
// "updateTime": "1706891359098",
// "orderCategory": "normal",
// "tpTriggerPrice": null,
// "tpOrderPrice": null,
// "slTriggerPrice": null,
// "slOrderPrice": null,
// "cancelSource": "not_canceled",
// "cancelSourceReason": null,
// "brokerId": "ec6dd3a7dd982d0b"
// "filled_amount": "1.000000000000000000", // filledAmount in "ws" watchOrders
// "cancelSource": "", // only in WS
// "instType": "SWAP", // only in WS
// }
//
const id = this.safeStringN(order, ['tpslId', 'orderId', 'algoId']);
const timestamp = this.safeInteger(order, 'createTime');
const lastUpdateTimestamp = this.safeInteger(order, 'updateTime');
const lastTradeTimestamp = this.safeInteger(order, 'fillTime');
const side = this.safeString(order, 'side');
let type = this.safeString(order, 'orderType');
let postOnly = undefined;
let timeInForce = undefined;
if (type === 'post_only') {
postOnly = true;
type = 'limit';
}
else if (type === 'fok') {
timeInForce = 'FOK';
type = 'limit';
}
else if (type === 'ioc') {
timeInForce = 'IOC';
type = 'limit';
}
const marketId = this.safeString(order, 'instId');
market = this.safeMarket(marketId, market);
const symbol = this.safeSymbol(marketId, market, '-');
const filled = this.safeString(order, 'filledSize');
const price = this.safeString2(order, 'px', 'price');
const average = this.safeString(order, 'averagePrice');
const status = this.parseOrderStatus(this.safeString(order, 'state'));
const feeCostString = this.safeString(order, 'fee');
const amount = this.safeString(order, 'size');
const leverage = this.safeString(order, 'leverage', '1');
const contractSize = this.safeString(market, 'contractSize');
const baseAmount = Precise["default"].stringMul(contractSize, filled);
let cost = undefined;
if (average !== undefined) {
cost = Precise["default"].stringMul(average, baseAmount);
cost = Precise["default"].stringDiv(cost, leverage);
}
// spot market buy: "sz" can refer either to base currency units or to quote currency units
let fee = undefined;
if (feeCostString !== undefined) {
const feeCostSigned = Precise["default"].stringAbs(feeCostString);
const feeCurrencyId = this.safeString(order, 'feeCcy', 'USDT');
const feeCurrencyCode = this.safeCurrencyCode(feeCurrencyId);
fee = {
'cost': this.parseNumber(feeCostSigned),
'currency': feeCurrencyCode,
};
}
let clientOrderId = this.safeString(order, 'clientOrderId');
if ((clientOrderId !== undefined) && (clientOrderId.length < 1)) {
clientOrderId = undefined; // fix empty clientOrderId string
}
const stopLossTriggerPrice = this.safeNumber(order, 'slTriggerPrice');
const stopLossPrice = this.safeNumber(order, 'slOrderPrice');
const takeProfitTriggerPrice = this.safeNumber(order, 'tpTriggerPrice');
const takeProfitPrice = this.safeNumber(order, 'tpOrderPrice');
const reduceOnlyRaw = this.safeString(order, 'reduceOnly');
const reduceOnly = (reduceOnlyRaw === 'true');
return this.safeOrder({
'info': order,
'id': id,
'clientOrderId': clientOrderId,
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'lastTradeTimestamp': lastTradeTimestamp,
'lastUpdateTimestamp': lastUpdateTimestamp,
'symbol': symbol,
'type': type,
'timeInForce': timeInForce,
'postOnly': postOnly,
'side': side,
'price': price,
'stopLossTriggerPrice': stopLossTriggerPrice,
'takeProfitTriggerPrice': takeProfitTriggerPrice,
'stopLossPrice': stopLossPrice,
'takeProfitPrice': takeProfitPrice,
'average': average,
'cost': cost,
'amount': amount,
'filled': filled,
'remaining': undefined,
'status': status,
'fee': fee,
'trades': undefined,
'reduceOnly': reduceOnly,
}, market);
}
/**
* @method
* @name blofin#createOrder
* @description create a trade order
* @see https://blofin.com/docs#place-order
* @see https://blofin.com/docs#place-tpsl-order
* @param {string} symbol unified symbol of the market to create an order in
* @param {string} type 'market' or 'limit' or 'post_only' or 'ioc' or 'fok'
* @param {string} side 'buy' or 'sell'
* @param {float} amount how much of currency you want to trade in units of base currency
* @param {float} [price] the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.triggerPrice] the trigger price for a trigger order
* @param {bool} [params.reduceOnly] a mark to reduce the position size for margin, swap and future orders
* @param {bool} [params.postOnly] true to place a post only order
* @param {string} [params.marginMode] 'cross' or 'isolated', default is 'cross'
* @param {float} [params.stopLossPrice] stop loss trigger price (will use privatePostTradeOrderTpsl)
* @param {float} [params.takeProfitPrice] take profit trigger price (will use privatePostTradeOrderTpsl)
* @param {string} [params.positionSide] *stopLossPrice/takeProfitPrice orders only* 'long' or 'short' or 'net' default is 'net'
* @param {boolean} [params.hedged] if true, the positionSide will be set to long/short instead of net, default is false
* @param {string} [params.clientOrderId] a unique id for the order
* @param {object} [params.takeProfit] *takeProfit object in params* containing the triggerPrice at which the attached take profit order will be triggered
* @param {float} [params.takeProfit.triggerPrice] take profit trigger price
* @param {float} [params.takeProfit.price] take profit order price (if not provided the order will be a market order)
* @param {object} [params.stopLoss] *stopLoss object in params* containing the triggerPrice at which the attached stop loss order will be triggered
* @param {float} [params.stopLoss.triggerPrice] stop loss trigger price
* @param {float} [params.stopLoss.price] stop loss order price (if not provided the order will be a market order)
* @returns {object} an [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async createOrder(symbol, type, side, amount, price = undefined, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const tpsl = this.safeBool(params, 'tpsl', false);
params = this.omit(params, 'tpsl');
let method = undefined;
[method, params] = this.handleOptionAndParams(params, 'createOrder', 'method', 'privatePostTradeOrder');
const isStopLossPriceDefined = this.safeString(params, 'stopLossPrice') !== undefined;
const isTakeProfitPriceDefined = this.safeString(params, 'takeProfitPrice') !== undefined;
const isTriggerOrder = this.safeString(params, 'triggerPrice') !== undefined;
const isType2Order = (isStopLossPriceDefined || isTakeProfitPriceDefined);
let response = undefined;
const reduceOnly = this.safeBool(params, 'reduceOnly');
if (reduceOnly !== undefined) {
params['reduceOnly'] = reduceOnly ? 'true' : 'false';
}
if (tpsl || (method === 'privatePostTradeOrderTpsl') || isType2Order) {
const tpslRequest = this.createTpslOrderRequest(symbol, type, side, amount, price, params);
response = await this.privatePostTradeOrderTpsl(tpslRequest);
}
else if (isTriggerOrder || (method === 'privatePostTradeOrderAlgo')) {
const triggerRequest = this.createOrderRequest(symbol, type, side, amount, price, params);
response = await this.privatePostTradeOrderAlgo(triggerRequest);
}
else {
const request = this.createOrderRequest(symbol, type, side, amount, price, params);
response = await this.privatePostTradeOrder(request);
}
if (isTriggerOrder || (method === 'privatePostTradeOrderAlgo')) {
const dataDict = this.safeDict(response, 'data', {});
const triggerOrder = this.parseOrder(dataDict, market);
return triggerOrder;
}
const data = this.safeList(response, 'data', []);
const first = this.safeDict(data, 0);
const order = this.parseOrder(first, market);
order['type'] = type;
order['side'] = side;
return order;
}
createTpslOrderRequest(symbol, type, side, amount = undefined, price = undefined, params = {}) {
const market = this.market(symbol);
const positionSide = this.safeString(params, 'positionSide', 'net');
const request = {
'instId': market['id'],
'side': side,
'positionSide': positionSide,
'brokerId': this.safeString(this.options, 'brokerId', 'ec6dd3a7dd982d0b'),
};
if (amount !== undefined) {
request['size'] = this.amountToPrecision(symbol, amount);
}
const marginMode = this.safeString(params, 'marginMode', 'cross'); // cross or isolated
if (marginMode !== 'cross' && marginMode !== 'isolated') {
throw new errors.BadRequest(this.id + ' createTpslOrder() requires a marginMode parameter that must be either cross or isolated');
}
const stopLossPrice = this.safeString(params, 'stopLossPrice');
const takeProfitPrice = this.safeString(params, 'takeProfitPrice');
if (stopLossPrice !== undefined) {
request['slTriggerPrice'] = this.priceToPrecision(symbol, stopLossPrice);
if (type === 'market') {
request['slOrderPrice'] = '-1';
}
else {
request['slOrderPrice'] = this.priceToPrecision(symbol, price);
}
}
else if (takeProfitPrice !== undefined) {
request['tpTriggerPrice'] = this.priceToPrecision(symbol, takeProfitPrice);
if (type === 'market') {
request['tpOrderPrice'] = '-1';
}
else {
request['tpOrderPrice'] = this.priceToPrecision(symbol, price);
}
}
request['marginMode'] = marginMode;
params = this.omit(params, ['stopLossPrice', 'takeProfitPrice']);
return this.extend(request, params);
}
/**
* @method
* @name blofin#cancelOrder
* @description cancels an open order
* @see https://blofin.com/docs#cancel-order
* @see https://blofin.com/docs#cancel-tpsl-order
* @param {string} id order id
* @param {string} symbol unified symbol of the market the order was made in
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {boolean} [params.trigger] True if cancelling a trigger/conditional
* @param {boolean} [params.tpsl] True if cancelling a tpsl order
* @returns {object} An [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async cancelOrder(id, symbol = undefined, params = {}) {
if (symbol === undefined) {
throw new errors.ArgumentsRequired(this.id + ' cancelOrder() requires a symbol argument');
}
await this.loadMarkets();
const market = this.market(symbol);
const request = {
'instId': market['id'],
};
const isTrigger = this.safeBoolN(params, ['trigger'], false);
const isTpsl = this.safeBool2(params, 'tpsl', 'TPSL', false);
const clientOrderId = this.safeString(params, 'clientOrderId');
if (clientOrderId !== undefined) {
request['clientOrderId'] = clientOrderId;
}
else {
if (!isTrigger && !isTpsl) {
request['orderId'] = id.toString();
}
else if (isTpsl) {
request['tpslId'] = id.toString();
}
else if (isTrigger) {
request['algoId'] = id.toString();
}
}
const query = this.omit(params, ['orderId', 'clientOrderId', 'stop', 'trigger', 'tpsl']);
if (isTpsl) {
const tpslResponse = await this.cancelOrders([id], symbol, params);
const first = this.safeDict(tpslResponse, 0);
return first;
}
else if (isTrigger) {
const triggerResponse = await this.privatePostTradeCancelAlgo(this.extend(request, query));
const triggerData = this.safeDict(triggerResponse, 'data');
return this.parseOrder(triggerData, market);
}
const response = await this.privatePostTradeCancelOrder(this.extend(request, query));
const data = this.safeList(response, 'data', []);
const order = this.safeDict(data, 0);
return this.parseOrder(order, market);
}
/**
* @method
* @name blofin#createOrders
* @description create a list of trade orders
* @see https://blofin.com/docs#place-multiple-orders
* @param {Array} orders list of orders to create, each object should contain the parameters required by createOrder, namely symbol, type, side, amount, price and params
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} an [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async createOrders(orders, params = {}) {
await this.loadMarkets();
const ordersRequests = [];
for (let i = 0; i < orders.length; i++) {
const rawOrder = orders[i];
const marketId = this.safeString(rawOrder, 'symbol');
const type = this.safeString(rawOrder, 'type');
const side = this.safeString(rawOrder, 'side');
const amount = this.safeValue(rawOrder, 'amount');
const price = this.safeValue(rawOrder, 'price');
const orderParams = this.safeDict(rawOrder, 'params', {});
const extendedParams = this.extend(orderParams, params); // the request does not accept extra params since it's a list, so we're extending each order with the common params
const orderRequest = this.createOrderRequest(marketId, type, side, amount, price, extendedParams);
ordersRequests.push(orderRequest);
}
const response = await this.privatePostTradeBatchOrders(ordersRequests);
const data = this.safeList(response, 'data', []);
return this.parseOrders(data);
}
/**
* @method
* @name blofin#fetchOpenOrders
* @description Fetch orders that are still open
* @see https://blofin.com/docs#get-active-orders
* @see https://blofin.com/docs#get-active-tpsl-orders
* @see https://docs.blofin.com/index.html#get-active-algo-orders
* @param {string} symbol unified market symbol
* @param {int} [since] the earliest time in ms to fetch open orders for
* @param {int} [limit] the maximum number of open orders structures to retrieve
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {bool} [params.trigger] True if fetching trigger or conditional orders
* @param {boolean} [params.paginate] default false, when true will automatically paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
* @returns {Order[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async fetchOpenOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
let paginate = false;
[paginate, params] = this.handleOptionAndParams(params, 'fetchOpenOrders', 'paginate');
if (paginate) {
return await this.fetchPaginatedCallDynamic('fetchOpenOrders', symbol, since, limit, params);
}
const request = {};
let market = undefined;
if (symbol !== undefined) {
market = this.market(symbol);
request['instId'] = market['id'];
}
if (limit !== undefined) {
request['limit'] = limit; // default 100, max 100
}
const isTrigger = this.safeBoolN(params, ['stop', 'trigger'], false);
const isTpSl = this.safeBool2(params, 'tpsl', 'TPSL', false);
let method = undefined;
[method, params] = this.handleOptionAndParams(params, 'fetchOpenOrders', 'method', 'privateGetTradeOrdersPending');
const query = this.omit(params, ['method', 'stop', 'trigger', 'tpsl', 'TPSL']);
let response = undefined;
if (isTpSl || (method === 'privateGetTradeOrdersTpslPending')) {
response = await this.privateGetTradeOrdersTpslPending(this.extend(request, query));
}
else if (isTrigger || (method === 'privateGetTradeOrdersAlgoPending')) {
request['orderType'] = 'trigger';
response = await this.privateGetTradeOrdersAlgoPending(this.extend(request, query));
}
else {
response = await this.privateGetTradeOrdersPending(this.extend(request, query));
}
const data = this.safeList(response, 'data', []);
return this.parseOrders(data, market, since, limit);
}
/**
* @method
* @name blofin#fetchMyTrades
* @description fetch all trades made by the user
* @see https://blofin.com/docs#get-trade-history
* @param {string} symbol unified market symbol
* @param {int} [since] the earliest time in ms to fetch trades for
* @param {int} [limit] the maximum number of trades structures to retrieve
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {int} [params.until] Timestamp in ms of the latest time to retrieve trades for
* @param {boolean} [params.paginate] default false, when true will automatically paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
* @returns {Trade[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure}
*/
async fetchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
let paginate = false;
[paginate, params] = this.handleOptionAndParams(params, 'fetchMyTrades', 'paginate');
if (paginate) {
return await this.fetchPaginatedCallDynamic('fetchMyTrades', symbol, since, limit, params);
}
let request = {};
let market = undefined;
if (symbol !== undefined) {
market = this.market(symbol);
request['instId'] = market['id'];
}
[request, params] = this.handleUntilOption('end', request, params);
if (limit !== undefined) {
request['limit'] = limit; // default 100, max 100
}
const response = await this.privateGetTradeFillsHistory(this.extend(request, params));
const data = this.safeList(response, 'data', []);
return this.parseTrades(data, market, since, limit);
}
/**
* @method
* @name blofin#fetchDeposits
* @description fetch all deposits made to an account
* @see https://blofin.com/docs#get-deposite-history
* @param {string} code unified currency code
* @param {int} [since] the earliest time in ms to fetch deposits for
* @param {int} [limit] the maximum number of deposits structures to retrieve
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {int} [params.until] the latest time in ms to fetch entries for
* @param {boolean} [params.paginate] default false, when true will automatically paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
* @returns {object[]} a list of [transaction structures]{@link https://docs.ccxt.com/#/?id=transaction-structure}
*/
async fetchDeposits(code = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
let paginate = false;
[paginate, params] = this.handleOptionAndParams(params, 'fetchDeposits', 'paginate');
if (paginate) {
return await this.fetchPaginatedCallDynamic('fetchDeposits', code, since, limit, params);
}
let request = {};
let currency = undefined;
if (code !== undefined) {
currency = this.currency(code);
request['currency'] = currency['id'];
}
if (since !== undefined) {
request['before'] = Math.max(since - 1, 0);
}
if (limit !== undefined) {
request['limit'] = limit; // default 100, max 100
}
[request, params] = this.handleUntilOption('after', request, params);
const response = await this.privateGetAssetDepositHistory(this.extend(request, params));
const data = this.safeList(response, 'data', []);
return this.parseTransactions(data, currency, since, limit, params);
}
/**
* @method
* @name blofin#fetchWithdrawals
* @description fetch all withdrawals made from an account
* @see https://blofin.com/docs#get-withdraw-history
* @param {string} code unified currency code
* @param {int} [since] the earliest time in ms to fetch withdrawals for
* @param {int} [limit] the maximum number of withdrawals structures to retrieve
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {int} [params.until] the latest time in ms to fetch entries for
* @param {boolean} [params.paginate] default false, when true will automatically paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
* @returns {object[]} a list of [transaction structures]{@link https://docs.ccxt.com/#/?id=transaction-structure}
*/
async fetchWithdrawals(code = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
let paginate = false;
[paginate, params] = this.handleOptionAndParams(params, 'fetchWithdrawals', 'paginate');
if (paginate) {
return await this.fetchPaginatedCallDynamic('fetchWithdrawals', code, since, limit, params);
}
let request = {};
let currency = undefined;
if (code !== undefined) {
currency = this.currency(code);
request['currency'] = currency['id'];
}
if (since !== undefined) {
request['before'] = Math.max(since - 1, 0);
}
if (limit !== undefined) {
request['limit'] = limit; // default 100, max 100
}
[request, params] = this.handleUntilOption('after', request, params);
const response = await this.privateGetAssetWithdrawalHistory(this.extend(request, params));
const data = this.safeList(response, 'data', []);
return this.parseTransactions(data, currency, since, limit, params);
}
/**
* @method
* @name blofin#fetchLedger
* @description fetch the history of changes, actions done by the user or operations that altered the balance of the user
* @see https://blofin.com/docs#get-funds-transfer-history
* @param {string} [code] unified currency code, default is undefined
* @param {int} [since] timestamp in ms of the earliest ledger entry, default is undefined
* @param {int} [limit] max number of ledger entries to return, default is undefined
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.marginMode] 'cross' or 'isolated'
* @param {int} [params.until] the latest time in ms to fetch entries for
* @param {boolean} [params.paginate] default false, when true will automatically paginate by calling this endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
* @returns {object} a [ledger structure]{@link https://docs.ccxt.com/#/?id=ledger}
*/
async fetchLedger(code = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
let paginate = false;
[paginate, params] = this.handleOptionAndParams(params, 'fetchLedger', 'paginate');
if (paginate) {
return await this.fetchPaginatedCallDynamic('fetchLedger', code, since, limit, params);
}
let request = {};
if (limit !== undefined) {
request['limit'] = limit;
}
let currency = undefined;
if (code !== undefined) {
currency = this.currency(code);
request['currency'] = currency['id'];
}
[request, params] = this.handleUntilOption('end', request, params);
let response = undefined;
response = await this.privateGetAssetBills(this.extend(request, params));
const data = this.safeList(response, 'data', []);
return this.parseLedger(data, currency, since, limit);
}
parseTransaction(transaction, currency = undefined) {
//
//
// fetchDeposits
//
// {
// "currency": "USDT",
// "chain": "TRC20",
// "address": "TGfJLtnsh3B9EqekFEBZ1nR14QanBUf5Bi",
// "txId": "892f4e0c32268b29b2e541ef30d32a30bbf10f902adcc4b1428319ed7c3758fd",
// "type": "0",
// "amount": "86.975843",
// "state": "1",
// "ts": "1703163304153",
// "tag": null,
// "confirm": "16",
// "depositId": "36c8e2a7ea184a219de72215a696acaf"
// }
// fetchWithdrawals
// {
// "currency": "USDT",
// "chain": "TRC20",
// "address": "TYgB3sVXHPEDQUu288EG1uMFh9Pk2swLgW",
// "txId": "1fd5ac52df414d7ea66194cadd9a5b4d2422c2b9720037f66d98207f9858fd96",
// "type": "0",
// "amount": "9",
// "fee": "1",
// "feeCurrency": "USDT",
// "state": "3",
// "clientId": null,
// "ts": "1707217439351",
// "tag": null,
// "memo": null,
// "withdrawId": "e0768698cfdf4aee8e54654c3775914b"
// }
//
let type = undefined;
let id = undefined;
const withdrawalId = this.safeString(transaction, 'withdrawId');
const depositId = this.safeString(transaction, 'depositId');
const addressTo = this.safeString(transaction, 'address');
const address = addressTo;
const tagTo = this.safeString(transaction, 'tag');
if (withdrawalId !== undefined) {
type = 'withdrawal';
id = withdrawalId;
}
else {
id = depositId;
type = 'deposit';
}
const currencyId = this.safeString(transaction, 'currency');
const code = this.safeCurrencyCode(currencyId);
const amount = this.safeNumber(transaction, 'amount');
const status = this.parseTransactionStatus(this.safeString(transaction, 'state'));
const txid = this.safeString(transaction, 'txId');
const timestamp = this.safeInteger(transaction, 'ts');
const feeCurrencyId = this.safeString(transaction, 'feeCurrency');
const feeCode = this.safeCurrencyCode(feeCurrencyId);
const feeCost = this.safeNumber(transaction, 'fee');
return {
'info': transaction,
'id': id,
'currency': code,
'amount': amount,
'network': undefined,
'addressFrom': undefined,
'addressTo': addressTo,
'address': address,
'tagFrom': undefined,
'tagTo': tagTo,
'tag': tagTo,
'status': status,
'type': type,
'updated': undefined,
'txid': txid,
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'internal': undefined,
'comment': undefined,
'fee': {
'currency': feeCode,
'cost': feeCost,
},
};
}
parseTransactionStatus(status) {
const statuses = {
'0': 'pending',
'1': 'ok',
'2': 'failed',
'3': 'pending',
};
return this.safeString(statuses, status, status);
}
parseLedgerEntryType(type) {
const types = {
'1': 'transfer',
'2': 'trade',
'3': 'trade',
'4': 'rebate',
'5': 'trade',
'6': 'transfer',
'7': 'trade',
'8': 'fee',
'9': 'trade',
'10': 'trade',
'11': 'trade', // system token conversion
};
return this.safeString(types, type, type);
}
parseLedgerEntry(item, currency = undefined) {
const currencyId = this.safeString(item, 'currency');
const code = this.safeCurrencyCode(currencyId, currency);
currency = this.safeCurrency(currencyId, currency);
const timestamp = this.safeInteger(item, 'ts');
return this.safeLedgerEntry({
'info': item,
'id': this.safeString(item, 'transferId'),
'direction': undefined,
'account': undefined,
'referenceId': this.safeString(item, 'clientId'),
'referenceAccount': undefined,
'type': this.parseLedgerEntryType(this.safeString(item, 'type')),
'currency': code,
'amount': this.safeNumber(item, 'amount'),
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'before': undefined,
'after': undefined,
'status': 'ok',
'fee': undefined,
}, currency);
}
parseIds(ids) {
/**
* @ignore
* @method
* @name blofin#parseIds
* @param {string[]|string} ids order ids
* @returns {string[]} list of order ids
*/
if (typeof ids === 'string') {
return ids.split(',');
}
else {
return ids;
}
}
/**
* @method
* @name blofin#cancelOrders
* @description cancel multiple orders
* @see https://blofin.com/docs#cancel-multiple-orders
* @param {string[]} ids order ids
* @param {string} symbol unified market symbol
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {boolean} [params.trigger] whether the order is a stop/trigger order
* @returns {object} an list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async cancelOrders(ids, symbol = undefined, params = {}) {
// TODO : the original endpoint signature differs, according to that you can skip individual symbol and assign ids in batch. At this moment, `params` is not being used too.
if (symbol === undefined) {
throw new errors.ArgumentsRequired(this.id + ' cancelOrders() requires a symbol argument');
}
await this.loadMarkets();
const market = this.market(symbol);
const request = [];
const options = this.safeDict(this.options, 'cancelOrders', {});
const defaultMethod = this.safeString(options, 'method', 'privatePostTradeCancelBatchOrders');
let method = this.safeString(params, 'method', defaultMethod);
const clientOrderIds = this.parseIds(this.safeValue(params, 'clientOrderId'));
const tpslIds = this.parseIds(this.safeValue(params, 'tpslId'));
const trigger = this.safeBoolN(params, ['stop', 'trigger', 'tpsl']);
if (trigger) {
method = 'privatePostTradeCancelTpsl';
}
if (clientOrderIds === undefined) {
ids = this.parseIds(ids);
if (tpslIds !== undefined) {
for (let i = 0; i < tpslIds.length; i++) {
request.push({
'tpslId': tpslIds[i],
'instId': market['id'],
});
}
}
for (let i = 0; i < ids.length; i++) {
if (trigger) {
request.push({
'tpslId': ids[i],
'instId': market['id'],
});
}
else {
request.push({
'orderId': ids[i],
'instId': market['id'],
});
}
}
}
else {
for (let i = 0; i < clientOrderIds.length; i++) {
request.push({
'instId': market['id'],
'clientOrderId': clientOrderIds[i],
});
}
}
let response = undefined;
if (method === 'privatePostTradeCancelTpsl') {
response = await this.privatePostTradeCancelTpsl(request); // * dont extend with params, otherwise ARRAY will be turned into OBJECT
}
else {
response = await this.privatePostTradeCancelBatchOrders(request); // * dont extend with params, otherwise ARRAY will be turned into OBJECT
}
const ordersData = this.safeList(response, 'data', []);
return this.parseOrders(ordersData, market, undefined, undefined, params);
}
/**
* @method
* @name blofin#transfer
* @description transfer currency internally between wallets on the same account
* @see https://blofin.com/docs#funds-transfer
* @param {string} code unified currency code
* @param {float} amount amount to transfer
* @param {string} fromAccount account to transfer from (funding, swap, copy_trading, earn)
* @param {string} toAccount account to transfer to (funding, swap, copy_trading, earn)
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a [transfer structure]{@link https://docs.ccxt.com/#/?id=transfer-structure}
*/
async transfer(code, amount, fromAccount, toAccount, params = {}) {
await this.loadMarkets();
const currency = this.currency(code);
const accountsByType = this.safeDict(this.options, 'accountsByType', {});
const fromId = this.safeString(accountsByType, fromAccount, fromAccount);
const toId = this.safeString(accountsByType, toAccount, toAccount);
const request = {
'currency': currency['id'],
'amount': this.currencyToPrecision(code, amount),
'fromAccount': fromId,
'toAccount': toId,
};
const response = await this.privatePostAssetTransfer(this.extend(request, params));
const data = this.safeDict(response, 'data', {});
return this.parseTransfer(data, currency);
}
parseTransfer(transfer, currency = undefined) {
const id = this.safeString(transfer, 'transferId');
return {
'info': transfer,
'id': id,
'timestamp': undefined,
'datetime': undefined,
'currency': undefined,
'amount': undefined,
'fromAccount': undefined,
'toAccount': undefined,
'status': undefined,
};
}
/**
* @method
* @name blofin#fetchPosition
* @description fetch data on a single open contract trade position
* @see https://blofin.com/docs#get-positions
* @param {string} symbol unified market symbol of the market the position is held in, default is undefined
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.instType] MARGIN, SWAP, FUTURES, OPTION
* @returns {object} a [position structure]{@link https://docs.ccxt.com/#/?id=position-structure}
*/
async fetchPosition(symbol, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const request = {
'instId': market['id'],
};
const response = await this.privateGetAccountPositions(this.extend(request, params));
const data = this.safeList(response, 'data', []);
const position = this.safeDict(data, 0);
if (position === undefined) {
return undefined;
}
return this.parsePosition(position, market);
}
/**
* @method
* @name blofin#fetchPositions
* @description fetch data on a single open contract trade position
* @see https://blofin.com/docs#get-positions
* @param {string[]} [symbols] list of unified market symbols
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.instType] MARGIN, SWAP, FUTURES, OPTION
* @returns {object} a [position structure]{@link https://docs.ccxt.com/#/?id=position-structure}
*/
async fetchPositions(symbols = undefined, params = {}) {
await this.loadMarkets();
symbols = this.marketSymbols(symbols);
const response = await this.privateGetAccountPositions(params);
const data = this.safeList(response, 'data', []);
const result = this.parsePositions(data);
return this.filterByArrayPositions(result, 'symbol', symbols, false);
}
parsePosition(position, market = undefined) {
//
// response similar for REST & WS
//
// {
// instType: 'SWAP',
// instId: 'LTC-USDT',
// marginMode: 'cross',
// positionId: '644159',
// positionSide: 'net',
// positions: '1',
// availablePositions: '1',
// averagePrice: '68.16',
// unrealizedPnl: '0.80631223',
// unrealizedPnlRatio: '0.03548909463028169',
// leverage: '3',
// liquidationPrice: '10.116655172370356435',
// markPrice: '68.96',
// initialMargin: '22.988770743333333333',
// margin: '', // this field might not exist in rest response
// marginRatio: '152.523509620342499273',
// maintenanceMargin: '0.34483156115',
// adl: '4',
// createTime: '1707235776528',
// updateTime: '1707235776528'
// }
//
const marketId = this.safeString(position, 'instId');
market = this.safeMarket(marketId, market);
const symbol = market['symbol'];
const pos = this.safeString(position, 'positions');
const contractsAbs = Precise["default"].stringAbs(pos);
let side = this.safeString(position, 'positionSide');
const hedged = side !== 'net';
const contracts = this.parseNumber(contractsAbs);
if (pos !== undefined) {
if (side === 'net') {
if (Precise["default"].stringGt(pos, '0')) {
side = 'long';
}
else if (Precise["default"].stringLt(pos, '0')) {
side = 'short';
}
else {
side = undefined;
}
}
}
const contractSize = this.safeNumber(market, 'contractSize');
const contractSizeString = this.numberToString(contractSize);
const markPriceString = this.safeString(position, 'markPrice');
let notionalString = this.safeString(position, 'notionalUsd');
if (market['inverse']) {
notionalString = Precise["default"].stringDiv(Precise["default"].stringMul(contractsAbs, contractSizeString), markPriceString);
}
const notional = this.parseNumber(notionalString);
const marginMode = this.safeString(position, 'marginMode');
let initialMarginString = undefined;
const entryPriceString = this.safeString(position, 'averagePrice');
const unrealizedPnlString = this.safeString(position, 'unrealizedPnl');
const leverageString = this.safeString(position, 'leverage');
let initialMarginPercentage = undefined;
let collateralString = undefined;
if (marginMode === 'cross') {
initialMarginString = this.safeString(position, 'initialMargin');
collateralString = Precise["default"].stringAdd(initialMarginString, unrealizedPnlString);
}
else if (marginMode === 'isolated') {
initialMarginPercentage = Precise["default"].stringDiv('1', leverageString);
collateralString = this.safeString(position, 'margin');
}
const maintenanceMarginString = this.safeString(position, 'maintenanceMargin');
const maintenanceMargin = this.parseNumber(maintenanceMarginString);
const maintenanceMarginPercentageString = Precise["default"].stringDiv(maintenanceMarginString, notionalString);
if (initialMarginPercentage === undefined) {
initialMarginPercentage = this.parseNumber(Precise["default"].stringDiv(initialMarginString, notionalString, 4));
}
else if (initialMarginString === undefined) {
initialMarginString = Precise["default"].stringMul(initialMarginPercentage, notionalString);
}
const rounder = '0.00005'; // round to closest 0.01%
const maintenanceMarginPercentage = this.parseNumber(Precise["default"].stringDiv(Precise["default"].stringAdd(maintenanceMarginPercentageString, rounder), '1', 4));
const liquidationPrice = this.safeNumber(position, 'liquidationPrice');
const percentageString = this.safeString(position, 'unrealizedPnlRatio');
const percentage = this.parseNumber(Precise["default"].stringMul(percentageString, '100'));
const timestamp = this.safeInteger(position, 'updateTime');
const marginRatio = this.parseNumber(Precise["default"].stringDiv(maintenanceMarginString, collateralString, 4));
return this.safePosition({
'info': position,
'id': undefined,
'symbol': symbol,
'notional': notional,
'marginMode': marginMode,
'liquidationPrice': liquidationPrice,
'entryPrice': this.parseNumber(entryPriceString),
'unrealizedPnl': this.parseNumber(unrealizedPnlString),
'percentage': percentage,
'contracts': contracts,
'contractSize': contractSize,
'markPrice': this.parseNumber(markPriceString),
'lastPrice': undefined,
'side': side,
'hedged': hedged,
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'lastUpdateTimestamp': undefined,
'maintenanceMargin': maintenanceMargin,
'maintenanceMarginPercentage': maintenanceMarginPercentage,
'collateral': this.parseNumber(collateralString),
'initialMargin': this.parseNumber(initialMarginString),
'initialMarginPercentage': this.parseNumber(initialMarginPercentage),
'leverage': this.parseNumber(leverageString),
'marginRatio': marginRatio,
'stopLossPrice': undefined,
'takeProfitPrice': undefined,
});
}
/**
* @method
* @name blofin#fetchLeverages
* @description fetch the set leverage for all contract markets
* @see https://docs.blofin.com/index.html#get-multiple-leverage
* @param {string[]} symbols a list of unified market symbols, required on blofin
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.marginMode] 'cross' or 'isolated'
* @returns {object} a list of [leverage structures]{@link https://docs.ccxt.com/#/?id=leverage-structure}
*/
async fetchLeverages(symbols = undefined, params = {}) {
await this.loadMarkets();
if (symbols === undefined) {
throw new errors.ArgumentsRequired(this.id + ' fetchLeverages() requires a symbols argument');
}
let marginMode = undefined;
[marginMode, params] = this.handleMarginModeAndParams('fetchLeverages', params);
if (marginMode === undefined) {
marginMode = this.safeString(params, 'marginMode', 'cross'); // cross as default marginMode
}
if ((marginMode !== 'cross') && (marginMode !== 'isolated')) {
throw new errors.BadRequest(this.id + ' fetchLeverages() requires a marginMode parameter that must be either cross or isolated');
}
symbols = this.marketSymbols(symbols);
let instIds = '';
for (let i = 0; i < symbols.length; i++) {
const entry = symbols[i];
const entryMarket = this.market(entry);
if (i > 0) {
instIds = instIds + ',' + entryMarket['id'];
}
else {
instIds = instIds + entryMarket['id'];
}
}
const request = {
'instId': instIds,
'marginMode': marginMode,
};
const response = await this.privateGetAccountBatchLeverageInfo(this.extend(request, params));
//
// {
// "code": "0",
// "msg": "success",
// "data": [
// {
// "leverage": "3",
// "marginMode": "cross",
// "instId": "BTC-USDT"
// },
// ]
// }
//
const leverages = this.safeList(response, 'data', []);
return this.parseLeverages(leverages, symbols, 'instId');
}
/**
* @method
* @name blofin#fetchLeverage
* @description fetch the set leverage for a market
* @see https://docs.blofin.com/index.html#get-leverage
* @param {string} symbol unified market symbol
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.marginMode] 'cross' or 'isolated'
* @returns {object} a [leverage structure]{@link https://docs.ccxt.com/#/?id=leverage-structure}
*/
async fetchLeverage(symbol, params = {}) {
await this.loadMarkets();
let marginMode = undefined;
[marginMode, params] = this.handleMarginModeAndParams('fetchLeverage', params);
if (marginMode === undefined) {
marginMode = this.safeString(params, 'marginMode', 'cross'); // cross as default marginMode
}
if ((marginMode !== 'cross') && (marginMode !== 'isolated')) {
throw new errors.BadRequest(this.id + ' fetchLeverage() requires a marginMode parameter that must be either cross or isolated');
}
const market = this.market(symbol);
const request = {
'instId': market['id'],
'marginMode': marginMode,
};
const response = await this.privateGetAccountLeverageInfo(this.extend(request, params));
//
// {
// "code": "0",
// "msg": "success",
// "data": {
// "leverage": "3",
// "marginMode": "cross",
// "instId": "BTC-USDT"
// }
// }
//
const data = this.safeDict(response, 'data', {});
return this.parseLeverage(data, market);
}
parseLeverage(leverage, market = undefined) {
const marketId = this.safeString(leverage, 'instId');
const leverageValue = this.safeInteger(leverage, 'leverage');
return {
'info': leverage,
'symbol': this.safeSymbol(marketId, market),
'marginMode': this.safeStringLower(leverage, 'marginMode'),
'longLeverage': leverageValue,
'shortLeverage': leverageValue,
};
}
/**
* @method
* @name blofin#setLeverage
* @description set the level of leverage for a market
* @see https://blofin.com/docs#set-leverage
* @param {int} leverage the rate of leverage
* @param {string} symbol unified market symbol
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.marginMode] 'cross' or 'isolated'
* @param {string} [params.positionSide] 'long' or 'short' - required for hedged mode in isolated margin
* @returns {object} response from the exchange
*/
async setLeverage(leverage, symbol = undefined, params = {}) {
if (symbol === undefined) {
throw new errors.ArgumentsRequired(this.id + ' setLeverage() requires a symbol argument');
}
// WARNING: THIS WILL INCREASE LIQUIDATION PRICE FOR OPEN ISOLATED LONG POSITIONS
// AND DECREASE LIQUIDATION PRICE FOR OPEN ISOLATED SHORT POSITIONS
if ((leverage < 1) || (leverage > 125)) {
throw new errors.BadRequest(this.id + ' setLeverage() leverage should be between 1 and 125');
}
await this.loadMarkets();
const market = this.market(symbol);
let marginMode = undefined;
[marginMode, params] = this.handleMarginModeAndParams('setLeverage', params, 'cross');
if ((marginMode !== 'cross') && (marginMode !== 'isolated')) {
throw new errors.BadRequest(this.id + ' setLeverage() requires a marginMode parameter that must be either cross or isolated');
}
const request = {
'leverage': leverage,
'marginMode': marginMode,
'instId': market['id'],
};
const response = await this.privatePostAccountSetLeverage(this.extend(request, params));
return response;
}
/**
* @method
* @name blofin#closePosition
* @description closes open positions for a market
* @see https://blofin.com/docs#close-positions
* @param {string} symbol Unified CCXT market symbol
* @param {string} [side] 'buy' or 'sell', leave as undefined in net mode
* @param {object} [params] extra parameters specific to the blofin api endpoint
* @param {string} [params.clientOrderId] a unique identifier for the order
* @param {string} [params.marginMode] 'cross' or 'isolated', default is 'cross;
* @param {string} [params.code] *required in the case of closing cross MARGIN position for Single-currency margin* margin currency
*
* EXCHANGE SPECIFIC PARAMETERS
* @param {boolean} [params.autoCxl] whether any pending orders for closing out needs to be automatically canceled when close position via a market order. false or true, the default is false
* @param {string} [params.tag] order tag a combination of case-sensitive alphanumerics, all numbers, or all letters of up to 16 characters
* @returns {object[]} [A list of position structures]{@link https://docs.ccxt.com/#/?id=position-structure}
*/
async closePosition(symbol, side = undefined, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const clientOrderId = this.safeString(params, 'clientOrderId');
let marginMode = undefined;
[marginMode, params] = this.handleMarginModeAndParams('closePosition', params, 'cross');
const request = {
'instId': market['id'],
'marginMode': marginMode,
};
if (clientOrderId !== undefined) {
request['clientOrderId'] = clientOrderId;
}
const response = await this.privatePostTradeClosePosition(this.extend(request, params));
return this.safeDict(response, 'data');
}
/**
* @method
* @name blofin#fetchClosedOrders
* @description fetches information on multiple closed orders made by the user
* @see https://blofin.com/docs#get-order-history
* @see https://blofin.com/docs#get-tpsl-order-history
* @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 orde structures to retrieve
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {bool} [params.trigger] True if fetching trigger or conditional orders
* @param {boolean} [params.paginate] default false, when true will automatically paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
* @returns {Order[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async fetchClosedOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
let paginate = false;
[paginate, params] = this.handleOptionAndParams(params, 'fetchClosedOrders', 'paginate');
if (paginate) {
return await this.fetchPaginatedCallDynamic('fetchClosedOrders', symbol, since, limit, params);
}
const request = {};
let market = undefined;
if (symbol !== undefined) {
market = this.market(symbol);
request['instId'] = market['id'];
}
if (limit !== undefined) {
request['limit'] = limit; // default 100, max 100
}
if (since !== undefined) {
request['begin'] = since;
}
const isTrigger = this.safeBoolN(params, ['stop', 'trigger', 'tpsl', 'TPSL'], false);
let method = undefined;
[method, params] = this.handleOptionAndParams(params, 'fetchOpenOrders', 'method', 'privateGetTradeOrdersHistory');
const query = this.omit(params, ['method', 'stop', 'trigger', 'tpsl', 'TPSL']);
let response = undefined;
if ((isTrigger) || (method === 'privateGetTradeOrdersTpslHistory')) {
response = await this.privateGetTradeOrdersTpslHistory(this.extend(request, query));
}
else {
response = await this.privateGetTradeOrdersHistory(this.extend(request, query));
}
const data = this.safeList(response, 'data', []);
return this.parseOrders(data, market, since, limit);
}
/**
* @method
* @name blofin#fetchMarginMode
* @description fetches the margin mode of a trading pair
* @see https://docs.blofin.com/index.html#get-margin-mode
* @param {string} symbol unified symbol of the market to fetch the margin mode for
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a [margin mode structure]{@link https://docs.ccxt.com/#/?id=margin-mode-structure}
*/
async fetchMarginMode(symbol, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const response = await this.privateGetAccountMarginMode(params);
//
// {
// "code": "0",
// "msg": "success",
// "data": {
// "marginMode": "cross"
// }
// }
//
const data = this.safeDict(response, 'data', {});
return this.parseMarginMode(data, market);
}
parseMarginMode(marginMode, market = undefined) {
return {
'info': marginMode,
'symbol': this.safeString(market, 'symbol'),
'marginMode': this.safeString(marginMode, 'marginMode'),
};
}
/**
* @method
* @name blofin#setMarginMode
* @description set margin mode to 'cross' or 'isolated'
* @see https://docs.blofin.com/index.html#set-margin-mode
* @param {string} marginMode 'cross' or 'isolated'
* @param {string} [symbol] unified market symbol (not used in blofin setMarginMode)
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} response from the exchange
*/
async setMarginMode(marginMode, symbol = undefined, params = {}) {
this.checkRequiredArgument('setMarginMode', marginMode, 'marginMode', ['cross', 'isolated']);
await this.loadMarkets();
let market = undefined;
if (symbol !== undefined) {
market = this.market(symbol);
}
const request = {
'marginMode': marginMode,
};
const response = await this.privatePostAccountSetMarginMode(this.extend(request, params));
//
// {
// "code": "0",
// "msg": "success",
// "data": {
// "marginMode": "isolated"
// }
// }
//
const data = this.safeDict(response, 'data', {});
return this.parseMarginMode(data, market);
}
/**
* @method
* @name blofin#fetchPositionMode
* @description fetchs the position mode, hedged or one way
* @see https://docs.blofin.com/index.html#get-position-mode
* @param {string} [symbol] unified symbol of the market to fetch the position mode for (not used in blofin fetchPositionMode)
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} an object detailing whether the market is in hedged or one-way mode
*/
async fetchPositionMode(symbol = undefined, params = {}) {
const response = await this.privateGetAccountPositionMode(params);
const data = this.safeDict(response, 'data', {});
const positionMode = this.safeString(data, 'positionMode');
//
// {
// "code": "0",
// "msg": "success",
// "data": {
// "positionMode": "long_short_mode"
// }
// }
//
return {
'info': data,
'hedged': positionMode === 'long_short_mode',
};
}
/**
* @method
* @name blofin#setPositionMode
* @description set hedged to true or false for a market
* @see https://docs.blofin.com/index.html#set-position-mode
* @param {bool} hedged set to true to use hedged mode, false for one-way mode
* @param {string} [symbol] not used by blofin setPositionMode ()
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} response from the exchange
*/
async setPositionMode(hedged, symbol = undefined, params = {}) {
const request = {
'positionMode': hedged ? 'long_short_mode' : 'net_mode',
};
//
// {
// "code": "0",
// "msg": "success",
// "data": {
// "positionMode": "net_mode"
// }
// }
//
return await this.privatePostAccountSetPositionMode(this.extend(request, params));
}
handleErrors(httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) {
if (response === undefined) {
return undefined; // fallback to default error handler
}
//
// {"code":"152002","msg":"Parameter bar error."}
//
const code = this.safeString(response, 'code');
const message = this.safeString(response, 'msg');
const feedback = this.id + ' ' + body;
if (code !== undefined && code !== '0') {
this.throwExactlyMatchedException(this.exceptions['exact'], message, feedback);
this.throwExactlyMatchedException(this.exceptions['exact'], code, feedback);
this.throwBroadlyMatchedException(this.exceptions['broad'], message, feedback);
throw new errors.ExchangeError(feedback); // unknown message
}
//
// {
// orderId: null,
// clientOrderId: '',
// msg: 'Order failed. Insufficient USDT margin in account',
// code: '103003'
// }
//
const data = this.safeList(response, 'data');
const first = this.safeDict(data, 0);
const insideMsg = this.safeString(first, 'msg');
const insideCode = this.safeString(first, 'code');
if (insideCode !== undefined && insideCode !== '0') {
this.throwExactlyMatchedException(this.exceptions['exact'], insideCode, feedback);
this.throwExactlyMatchedException(this.exceptions['exact'], insideMsg, feedback);
this.throwBroadlyMatchedException(this.exceptions['broad'], insideMsg, feedback);
}
return undefined;
}
sign(path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
let request = '/api/' + this.version + '/' + this.implodeParams(path, params);
const query = this.omit(params, this.extractParams(path));
let url = this.implodeHostname(this.urls['api']['rest']) + request;
// const type = this.getPathAuthenticationType (path);
if (api === 'public') {
if (!this.isEmpty(query)) {
url += '?' + this.urlencode(query);
}
}
else if (api === 'private') {
this.checkRequiredCredentials();
const timestamp = this.milliseconds().toString();
headers = {
'ACCESS-KEY': this.apiKey,
'ACCESS-PASSPHRASE': this.password,
'ACCESS-TIMESTAMP': timestamp,
'ACCESS-NONCE': timestamp,
};
let sign_body = '';
if (method === 'GET') {
if (!this.isEmpty(query)) {
const urlencodedQuery = '?' + this.urlencode(query);
url += urlencodedQuery;
request += urlencodedQuery;
}
}
else {
if (!this.isEmpty(query)) {
body = this.json(query);
sign_body = body;
}
headers['Content-Type'] = 'application/json';
}
const auth = request + method + timestamp + timestamp + sign_body;
const signature = this.stringToBase64(this.hmac(this.encode(auth), this.encode(this.secret), sha256.sha256));
headers['ACCESS-SIGN'] = signature;
}
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
}
}
module.exports = blofin;