# -*- coding: utf-8 -*-

# 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

from ccxt.async_support.base.exchange import Exchange
from ccxt.abstract.blofin import ImplicitAPI
import hashlib
from ccxt.base.types import Any, Balances, Currency, Int, LedgerEntry, Leverage, Leverages, MarginMode, Market, Num, Order, OrderBook, OrderRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, Trade, TradingFeeInterface, Transaction, TransferEntry
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class blofin(Exchange, ImplicitAPI):

    def describe(self) -> Any:
        return self.deep_extend(super(blofin, self).describe(), {
            'id': 'blofin',
            'name': 'BloFin',
            'countries': ['US'],
            'version': 'v1',
            'rateLimit': 100,
            'pro': True,
            'has': {
                'CORS': None,
                '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': None,
                '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': None,
                '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': None,
                'fetchOpenOrders': True,
                'fetchOrder': None,
                '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,  # todo new
                        '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': self.parse_number('0.00060'),
                    'maker': self.parse_number('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': None,
                    'fetchOpenOrders': {
                        'marginMode': False,
                        'limit': 100,
                        'trigger': True,
                        'trailing': False,
                        'symbolRequired': False,
                    },
                    'fetchOrders': None,
                    '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': None,
                        'triggerDirection': False,
                        'stopLossPrice': False,
                        'takeProfitPrice': False,
                        'attachedStopLossTakeProfit': None,
                        'hedged': False,
                    },
                },
                'forDerivatives': {
                    'extends': 'default',
                    'createOrder': {
                        'marginMode': True,
                        'triggerPrice': False,  # todo
                        'triggerPriceType': None,
                        'triggerDirection': False,
                        'stopLossPrice': True,
                        'takeProfitPrice': True,
                        'attachedStopLossTakeProfit': {
                            'triggerPriceType': None,
                            'price': True,
                        },
                        'hedged': True,
                    },
                },
                'swap': {
                    'linear': {
                        'extends': 'forDerivatives',
                    },
                    'inverse': None,
                },
                'future': {
                    'linear': None,
                    'inverse': None,
                },
            },
            'exceptions': {
                'exact': {
                    '400': BadRequest,  # Body can not be empty
                    '401': AuthenticationError,  # Invalid signature
                    '500': ExchangeError,  # Internal Server Error
                    '404': BadRequest,  # not found
                    '405': BadRequest,  # Method Not Allowed
                    '406': BadRequest,  # Not Acceptable
                    '429': RateLimitExceeded,  # Too Many Requests
                    '152001': BadRequest,  # Parameter {} cannot be empty
                    '152002': BadRequest,  # Parameter {} error
                    '152003': BadRequest,  # Either parameter {} or {} is required
                    '152004': BadRequest,  # JSON syntax error
                    '152005': BadRequest,  # Parameter error: wrong or empty
                    '152006': InvalidOrder,  # Batch orders can be placed for up to 20 at once
                    '152007': InvalidOrder,  # Batch orders can only be placed with the same instId and marginMode
                    '152008': InvalidOrder,  # Only the same field is allowed for bulk cancellation of orders, orderId is preferred
                    '152009': InvalidOrder,  # {} must be a combination of numbers, letters, or underscores, and the maximum length of characters is 32
                    '150003': InvalidOrder,  # clientId already exist
                    '150004': InvalidOrder,  # Insufficient balance. please adjust the amount and try again
                    '542': InvalidOrder,  # Exceeded the maximum order size limit
                    '102002': InvalidOrder,  # Duplicate customized order ID
                    '102005': InvalidOrder,  # Position had been closed
                    '102014': InvalidOrder,  # Limit order exceeds maximum order size limit
                    '102015': InvalidOrder,  # Market order exceeds maximum order size limit
                    '102022': InvalidOrder,  # Failed to place order. You don’t have any positions of self contract. Turn off Reduce-only to continue.
                    '102037': InvalidOrder,  # TP trigger price should be higher than the latest trading price
                    '102038': InvalidOrder,  # SL trigger price should be lower than the latest trading price
                    '102039': InvalidOrder,  # TP trigger price should be lower than the latest trading price
                    '102040': InvalidOrder,  # SL trigger price should be higher than the latest trading price
                    '102047': InvalidOrder,  # Stop loss trigger price should be higher than the order price
                    '102048': InvalidOrder,  # stop loss trigger price must be higher than the best bid price
                    '102049': InvalidOrder,  # Take profit trigger price should be lower than the order price
                    '102050': InvalidOrder,  # stop loss trigger price must be lower than the best ask price
                    '102051': InvalidOrder,  # stop loss trigger price should be lower than the order price
                    '102052': InvalidOrder,  # take profit trigger price should be higher than the order price
                    '102053': InvalidOrder,  # take profit trigger price should be lower than the best bid price
                    '102054': InvalidOrder,  # take profit trigger price should be higher than the best ask price
                    '102055': InvalidOrder,  # stop loss trigger price should be lower than the best ask price
                    '102064': BadRequest,  # Buy price is not within the price limit(Minimum: 310.40; Maximum:1,629.40)
                    '102065': BadRequest,  # Sell price is not within the price limit
                    '102068': BadRequest,  # Cancel failed order has been filled, triggered, canceled or does not exist
                    '103013': ExchangeError,  # Internal error; unable to process your request. Please try again.
                    'Order failed. Insufficient USDT margin in account': InsufficientFunds,  # Insufficient USDT margin in account
                },
                'broad': {
                    'Internal Server Error': ExchangeNotAvailable,  # {"code":500,"data":{},"detailMsg":"","error_code":"500","error_message":"Internal Server Error","msg":"Internal Server Error"}
                    'server error': ExchangeNotAvailable,  # {"code":500,"data":{},"detailMsg":"","error_code":"500","error_message":"server error 1236805249","msg":"server error 1236805249"}
                },
            },
            'httpExceptions': {
                '429': ExchangeNotAvailable,  # https://github.com/ccxt/ccxt/issues/9612
            },
            'precisionMode': 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',  # or 'privatePostTradeOrderTpsl'
                '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': None,
                    'pwd': None,  # password or pwd both work
                },
                'exchangeType': {
                    'spot': 'SPOT',
                    'swap': 'SWAP',
                    'SPOT': 'SPOT',
                    'SWAP': 'SWAP',
                },
            },
        })

    async def fetch_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all markets for blofin

        https://blofin.com/docs#get-instruments

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        response = await self.publicGetMarketInstruments(params)
        data = self.safe_list(response, 'data', [])
        return self.parse_markets(data)

    def parse_market(self, market: dict) -> Market:
        id = self.safe_string(market, 'instId')
        type = self.safe_string_lower(market, 'instType')
        spot = (type == 'spot')
        future = (type == 'future')
        swap = (type == 'swap')
        option = (type == 'option')
        contract = swap or future
        baseId = self.safe_string(market, 'baseCurrency')
        quoteId = self.safe_string(market, 'quoteCurrency')
        settleId = self.safe_string(market, 'quoteCurrency')
        settle = self.safe_currency_code(settleId)
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        symbol = base + '/' + quote
        if swap:
            symbol = symbol + ':' + settle
        expiry = None
        strikePrice = None
        optionType = None
        tickSize = self.safe_string(market, 'tickSize')
        fees = self.safe_dict_2(self.fees, type, 'trading', {})
        taker = self.safe_number(fees, 'taker')
        maker = self.safe_number(fees, 'maker')
        maxLeverage = self.safe_string(market, 'maxLeverage', '100')
        maxLeverage = Precise.string_max(maxLeverage, '1')
        isActive = (self.safe_string(market, 'state') == 'live')
        return self.safe_market_structure({
            'id': id,
            'symbol': symbol,
            'base': base,
            'quote': quote,
            'baseId': baseId,
            'quoteId': quoteId,
            'settle': settle,
            'settleId': settleId,
            'type': type,
            'spot': spot,
            'option': option,
            'margin': spot and (Precise.string_gt(maxLeverage, '1')),
            'swap': swap,
            'future': future,
            'active': isActive,
            'taker': taker,
            'maker': maker,
            'contract': contract,
            'linear': (quoteId == settleId) if contract else None,
            'inverse': (baseId == settleId) if contract else None,
            'contractSize': self.safe_number(market, 'contractValue') if contract else None,
            'expiry': expiry,
            'expiryDatetime': expiry,
            'strike': strikePrice,
            'optionType': optionType,
            'created': self.safe_integer(market, 'listTime'),
            'precision': {
                'amount': self.safe_number(market, 'lotSize'),
                'price': self.parse_number(tickSize),
            },
            'limits': {
                'leverage': {
                    'min': self.parse_number('1'),
                    'max': self.parse_number(maxLeverage),
                },
                'amount': {
                    'min': self.safe_number(market, 'minSize'),
                    'max': None,
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': None,
                    'max': None,
                },
            },
            'info': market,
        })

    async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data

        https://blofin.com/docs#get-order-book

        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'instId': market['id'],
        }
        limit = 50 if (limit is None) else limit
        if limit is not None:
            request['size'] = limit  # max 100
        response = await self.publicGetMarketBooks(self.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"
        #             }
        #         ]
        #     }
        #
        data = self.safe_list(response, 'data', [])
        first = self.safe_dict(data, 0, {})
        timestamp = self.safe_integer(first, 'ts')
        return self.parse_order_book(first, symbol, timestamp)

    def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        #
        # 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",
        #     }
        #
        timestamp = self.safe_integer(ticker, 'ts')
        marketId = self.safe_string(ticker, 'instId')
        market = self.safe_market(marketId, market, '-')
        symbol = market['symbol']
        last = self.safe_string(ticker, 'last')
        open = self.safe_string(ticker, 'open24h')
        spot = self.safe_bool(market, 'spot', False)
        quoteVolume = self.safe_string(ticker, 'volCurrency24h') if spot else None
        baseVolume = self.safe_string(ticker, 'vol24h')
        high = self.safe_string(ticker, 'high24h')
        low = self.safe_string(ticker, 'low24h')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': high,
            'low': low,
            'bid': self.safe_string(ticker, 'bidPrice'),
            'bidVolume': self.safe_string(ticker, 'bidSize'),
            'ask': self.safe_string(ticker, 'askPrice'),
            'askVolume': self.safe_string(ticker, 'askSize'),
            'vwap': None,
            'open': open,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'indexPrice': self.safe_string(ticker, 'indexPrice'),
            'markPrice': self.safe_string(ticker, 'markPrice'),
            'info': ticker,
        }, market)

    async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market

        https://blofin.com/docs#get-tickers

        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'instId': market['id'],
        }
        response = await self.publicGetMarketTickers(self.extend(request, params))
        data = self.safe_list(response, 'data', [])
        first = self.safe_dict(data, 0, {})
        return self.parse_ticker(first, market)

    async def fetch_mark_price(self, symbol: str, params={}) -> Ticker:
        """
        fetches mark price for the market

        https://docs.blofin.com/index.html#get-mark-price

        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.subType]: "linear" or "inverse"
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        response = await self.publicGetMarketMarkPrice(self.extend(request, params))
        data = self.safe_list(response, 'data', [])
        first = self.safe_dict(data, 0, {})
        return self.parse_ticker(first, market)

    async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market

        https://blofin.com/docs#get-tickers

        :param str[] [symbols]: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols)
        response = await self.publicGetMarketTickers(params)
        tickers = self.safe_list(response, 'data', [])
        return self.parse_tickers(tickers, symbols)

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        # 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": ""
        #   }
        #
        id = self.safe_string(trade, 'tradeId')
        marketId = self.safe_string(trade, 'instId')
        market = self.safe_market(marketId, market, '-')
        symbol = market['symbol']
        timestamp = self.safe_integer(trade, 'ts')
        price = self.safe_string_2(trade, 'price', 'fillPrice')
        amount = self.safe_string_2(trade, 'size', 'fillSize')
        side = self.safe_string(trade, 'side')
        orderId = self.safe_string(trade, 'orderId')
        feeCost = self.safe_string(trade, 'fee')
        fee = None
        if feeCost is not None:
            fee = {
                'cost': feeCost,
                'currency': market['settle'],
            }
        return self.safe_trade({
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': id,
            'order': orderId,
            'type': None,
            'takerOrMaker': None,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': None,
            'fee': fee,
        }, market)

    async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of most recent trades for a particular symbol

        https://blofin.com/docs#get-trades

        :param str 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 dict [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 self endpoint multiple times
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
        """
        await self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchTrades', 'paginate')
        if paginate:
            return await self.fetch_paginated_call_cursor('fetchTrades', symbol, since, limit, params, 'tradeId', 'after', None, 100)
        market = self.market(symbol)
        request: dict = {
            'instId': market['id'],
        }
        response = None
        if limit is not None:
            request['limit'] = limit  # default 100
        method = None
        method, params = self.handle_option_and_params(params, 'fetchTrades', 'method', 'publicGetMarketTrades')
        if method == 'publicGetMarketTrades':
            response = await self.publicGetMarketTrades(self.extend(request, params))
        data = self.safe_list(response, 'data', [])
        return self.parse_trades(data, market, since, limit)

    def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
        #
        #     [
        #         "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 [
            self.safe_integer(ohlcv, 0),
            self.safe_number(ohlcv, 1),
            self.safe_number(ohlcv, 2),
            self.safe_number(ohlcv, 3),
            self.safe_number(ohlcv, 4),
            self.safe_number(ohlcv, 6),
        ]

    async def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market

        https://blofin.com/docs#get-candlesticks

        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str 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 dict [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 self 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, open, high, low, close, volume
        """
        await self.load_markets()
        market = self.market(symbol)
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchOHLCV', 'paginate')
        if paginate:
            return await self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, 100)
        if limit is None:
            limit = 100  # default 100, max 100
        request: dict = {
            'instId': market['id'],
            'bar': self.safe_string(self.timeframes, timeframe, timeframe),
            'limit': limit,
        }
        until = self.safe_integer(params, 'until')
        if until is not None:
            request['after'] = until
            params = self.omit(params, 'until')
        response = None
        response = await self.publicGetMarketCandles(self.extend(request, params))
        data = self.safe_list(response, 'data', [])
        return self.parse_ohlcvs(data, market, timeframe, since, limit)

    async def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetches historical funding rate prices

        https://blofin.com/docs#get-funding-rate-history

        :param str 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 <https://docs.ccxt.com/#/?id=funding-rate-history-structure>` to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
        :returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument')
        await self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchFundingRateHistory', 'paginate')
        if paginate:
            return await self.fetch_paginated_call_deterministic('fetchFundingRateHistory', symbol, since, limit, '8h', params)
        market = self.market(symbol)
        request: dict = {
            'instId': market['id'],
        }
        if since is not None:
            request['before'] = max(since - 1, 0)
        if limit is not None:
            request['limit'] = limit
        response = await self.publicGetMarketFundingRateHistory(self.extend(request, params))
        rates = []
        data = self.safe_list(response, 'data', [])
        for i in range(0, len(data)):
            rate = data[i]
            timestamp = self.safe_integer(rate, 'fundingTime')
            rates.append({
                'info': rate,
                'symbol': market['symbol'],
                'fundingRate': self.safe_number(rate, 'fundingRate'),
                'timestamp': timestamp,
                'datetime': self.iso8601(timestamp),
            })
        sorted = self.sort_by(rates, 'timestamp')
        return self.filter_by_symbol_since_limit(sorted, market['symbol'], since, limit)

    def parse_funding_rate(self, contract, market: Market = None) -> FundingRate:
        #
        #    {
        #        "fundingRate": "0.00027815",
        #        "fundingTime": "1634256000000",
        #        "instId": "BTC-USD-SWAP",
        #    }
        #
        marketId = self.safe_string(contract, 'instId')
        symbol = self.safe_symbol(marketId, market)
        fundingTime = self.safe_integer(contract, 'fundingTime')
        # > The current interest is 0.
        return {
            'info': contract,
            'symbol': symbol,
            'markPrice': None,
            'indexPrice': None,
            'interestRate': self.parse_number('0'),
            'estimatedSettlePrice': None,
            'timestamp': None,
            'datetime': None,
            'fundingRate': self.safe_number(contract, 'fundingRate'),
            'fundingTimestamp': fundingTime,
            'fundingDatetime': self.iso8601(fundingTime),
            'nextFundingRate': None,
            'nextFundingTimestamp': None,
            'nextFundingDatetime': None,
            'previousFundingRate': None,
            'previousFundingTimestamp': None,
            'previousFundingDatetime': None,
            'interval': None,
        }

    async def fetch_funding_rate(self, symbol: str, params={}) -> FundingRate:
        """
        fetch the current funding rate

        https://blofin.com/docs#get-funding-rate

        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `funding rate structure <https://docs.ccxt.com/#/?id=funding-rate-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        if not market['swap']:
            raise ExchangeError(self.id + ' fetchFundingRate() is only valid for swap markets')
        request: dict = {
            'instId': market['id'],
        }
        response = await self.publicGetMarketFundingRate(self.extend(request, params))
        #
        #    {
        #        "code": "0",
        #        "data": [
        #            {
        #                "fundingRate": "0.00027815",
        #                "fundingTime": "1634256000000",
        #                "instId": "BTC-USD-SWAP",
        #            }
        #        ],
        #        "msg": ""
        #    }
        #
        data = self.safe_list(response, 'data', [])
        entry = self.safe_dict(data, 0, {})
        return self.parse_funding_rate(entry, market)

    def parse_balance_by_type(self, response):
        data = self.safe_list(response, 'data')
        if (data is not None) and isinstance(data, list):
            return self.parse_funding_balance(response)
        else:
            return self.parse_balance(response)

    def parse_balance(self, 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
        #             }
        #         ]
        #     }
        # }
        #
        result: dict = {'info': response}
        data = self.safe_dict(response, 'data', {})
        timestamp = self.safe_integer(data, 'ts')
        details = self.safe_list(data, 'details', [])
        for i in range(0, len(details)):
            balance = details[i]
            currencyId = self.safe_string(balance, 'currency')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            # it may be incorrect to use total, free and used for swap accounts
            eq = self.safe_string(balance, 'equity')
            availEq = self.safe_string(balance, 'available')
            if (eq is None) or (availEq is None):
                account['free'] = self.safe_string(balance, 'availableEquity')
                account['used'] = self.safe_string(balance, 'frozen')
            else:
                account['total'] = eq
                account['free'] = availEq
            result[code] = account
        result['timestamp'] = timestamp
        result['datetime'] = self.iso8601(timestamp)
        return self.safe_balance(result)

    def parse_funding_balance(self, response):
        #
        #  {
        #      "code": "0",
        #      "msg": "success",
        #      "data": [
        #          {
        #              "currency": "USDT",
        #              "balance": "10012514.919418081548717298",
        #              "available": "9872132.414278782284622898",
        #              "frozen": "138556.471805965930761067",
        #              "bonus": "0"
        #          }
        #      ]
        #  }
        #
        result: dict = {'info': response}
        data = self.safe_list(response, 'data', [])
        for i in range(0, len(data)):
            balance = data[i]
            currencyId = self.safe_string(balance, 'currency')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            # it may be incorrect to use total, free and used for swap accounts
            account['total'] = self.safe_string(balance, 'balance')
            account['free'] = self.safe_string(balance, 'available')
            account['used'] = self.safe_string(balance, 'frozen')
            result[code] = account
        return self.safe_balance(result)

    def parse_trading_fee(self, fee: dict, market: Market = None) -> TradingFeeInterface:
        return {
            'info': fee,
            'symbol': self.safe_symbol(None, market),
            # blofin returns the fees values opposed to other exchanges, so the sign needs to be flipped
            'maker': self.parse_number(Precise.string_neg(self.safe_string_2(fee, 'maker', 'makerU'))),
            'taker': self.parse_number(Precise.string_neg(self.safe_string_2(fee, 'taker', 'takerU'))),
            'percentage': None,
            'tierBased': None,
        }

    async def fetch_balance(self, params={}) -> Balances:
        """
        query for balance and get the amount of funds available for trading or funds locked in orders

        https://blofin.com/docs#get-balance
        https://blofin.com/docs#get-futures-account-balance

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.accountType]: the type of account to fetch the balance for, either 'funding' or 'futures'  or 'copy_trading' or 'earn'
        :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
        """
        await self.load_markets()
        accountType = None
        accountType, params = self.handle_option_and_params_2(params, 'fetchBalance', 'accountType', 'type')
        request: dict = {
        }
        response = None
        if accountType is not None and accountType != 'swap':
            options = self.safe_dict(self.options, 'accountsByType', {})
            parsedAccountType = self.safe_string(options, accountType, accountType)
            request['accountType'] = parsedAccountType
            response = await self.privateGetAssetBalances(self.extend(request, params))
        else:
            response = await self.privateGetAccountBalance(self.extend(request, params))
        return self.parse_balance_by_type(response)

    def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        market = self.market(symbol)
        request: dict = {
            'instId': market['id'],
            'side': side,
            'orderType': type,
            'size': self.amount_to_precision(symbol, amount),
            'brokerId': self.safe_string(self.options, 'brokerId', 'ec6dd3a7dd982d0b'),
        }
        marginMode = None
        marginMode, params = self.handle_margin_mode_and_params('createOrder', params, 'cross')
        request['marginMode'] = marginMode
        triggerPrice = self.safe_string(params, 'triggerPrice')
        timeInForce = self.safe_string(params, 'timeInForce', 'GTC')
        isHedged = self.safe_bool(params, 'hedged', False)
        if isHedged:
            request['positionSide'] = 'long' if (side == 'buy') else 'short'
        isMarketOrder = type == 'market'
        params = self.omit(params, ['timeInForce'])
        ioc = (timeInForce == 'IOC') or (type == 'ioc')
        marketIOC = (isMarketOrder and ioc)
        if isMarketOrder or marketIOC:
            request['orderType'] = 'market'
        else:
            key = 'orderPrice' if (triggerPrice is not None) else 'price'
            request[key] = self.price_to_precision(symbol, price)
        postOnly = False
        postOnly, params = self.handle_post_only(isMarketOrder, type == 'post_only', params)
        if postOnly:
            request['type'] = 'post_only'
        stopLoss = self.safe_dict(params, 'stopLoss')
        takeProfit = self.safe_dict(params, 'takeProfit')
        params = self.omit(params, ['stopLoss', 'takeProfit', 'hedged'])
        isStopLoss = stopLoss is not None
        isTakeProfit = takeProfit is not None
        if isStopLoss or isTakeProfit:
            if isStopLoss:
                slTriggerPrice = self.safe_string_2(stopLoss, 'triggerPrice', 'stopPrice')
                request['slTriggerPrice'] = self.price_to_precision(symbol, slTriggerPrice)
                slOrderPrice = self.safe_string(stopLoss, 'price', '-1')
                request['slOrderPrice'] = self.price_to_precision(symbol, slOrderPrice)
            if isTakeProfit:
                tpTriggerPrice = self.safe_string_2(takeProfit, 'triggerPrice', 'stopPrice')
                request['tpTriggerPrice'] = self.price_to_precision(symbol, tpTriggerPrice)
                tpPrice = self.safe_string(takeProfit, 'price', '-1')
                request['tpOrderPrice'] = self.price_to_precision(symbol, tpPrice)
        elif triggerPrice is not None:
            request['orderType'] = 'trigger'
            request['triggerPrice'] = self.price_to_precision(symbol, triggerPrice)
            if isMarketOrder:
                request['orderPrice'] = '-1'
        return self.extend(request, params)

    def parse_order_status(self, status: Str):
        statuses: dict = {
            'canceled': 'canceled',
            'order_failed': 'canceled',
            'live': 'open',
            'partially_filled': 'open',
            'filled': 'closed',
            'effective': 'closed',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order: dict, market: Market = None) -> Order:
        #
        # 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
        # }
        #
        id = self.safe_string_n(order, ['tpslId', 'orderId', 'algoId'])
        timestamp = self.safe_integer(order, 'createTime')
        lastUpdateTimestamp = self.safe_integer(order, 'updateTime')
        lastTradeTimestamp = self.safe_integer(order, 'fillTime')
        side = self.safe_string(order, 'side')
        type = self.safe_string(order, 'orderType')
        postOnly = None
        timeInForce = None
        if type == 'post_only':
            postOnly = True
            type = 'limit'
        elif type == 'fok':
            timeInForce = 'FOK'
            type = 'limit'
        elif type == 'ioc':
            timeInForce = 'IOC'
            type = 'limit'
        marketId = self.safe_string(order, 'instId')
        market = self.safe_market(marketId, market)
        symbol = self.safe_symbol(marketId, market, '-')
        filled = self.safe_string(order, 'filledSize')
        price = self.safe_string_2(order, 'px', 'price')
        average = self.safe_string(order, 'averagePrice')
        status = self.parse_order_status(self.safe_string(order, 'state'))
        feeCostString = self.safe_string(order, 'fee')
        amount = self.safe_string(order, 'size')
        leverage = self.safe_string(order, 'leverage', '1')
        contractSize = self.safe_string(market, 'contractSize')
        baseAmount = Precise.string_mul(contractSize, filled)
        cost: Str = None
        if average is not None:
            cost = Precise.string_mul(average, baseAmount)
            cost = Precise.string_div(cost, leverage)
        # spot market buy: "sz" can refer either to base currency units or to quote currency units
        fee = None
        if feeCostString is not None:
            feeCostSigned = Precise.string_abs(feeCostString)
            feeCurrencyId = self.safe_string(order, 'feeCcy', 'USDT')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': self.parse_number(feeCostSigned),
                'currency': feeCurrencyCode,
            }
        clientOrderId = self.safe_string(order, 'clientOrderId')
        if (clientOrderId is not None) and (len(clientOrderId) < 1):
            clientOrderId = None  # fix empty clientOrderId string
        stopLossTriggerPrice = self.safe_number(order, 'slTriggerPrice')
        stopLossPrice = self.safe_number(order, 'slOrderPrice')
        takeProfitTriggerPrice = self.safe_number(order, 'tpTriggerPrice')
        takeProfitPrice = self.safe_number(order, 'tpOrderPrice')
        reduceOnlyRaw = self.safe_string(order, 'reduceOnly')
        reduceOnly = (reduceOnlyRaw == 'true')
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.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': None,
            'status': status,
            'fee': fee,
            'trades': None,
            'reduceOnly': reduceOnly,
        }, market)

    async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}) -> Order:
        """
        create a trade order

        https://blofin.com/docs#place-order
        https://blofin.com/docs#place-tpsl-order

        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit' or 'post_only' or 'ioc' or 'fok'
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param str [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 str [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 str [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 str [params.clientOrderId]: a unique id for the order
        :param dict [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 dict [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 dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        tpsl = self.safe_bool(params, 'tpsl', False)
        params = self.omit(params, 'tpsl')
        method = None
        method, params = self.handle_option_and_params(params, 'createOrder', 'method', 'privatePostTradeOrder')
        isStopLossPriceDefined = self.safe_string(params, 'stopLossPrice') is not None
        isTakeProfitPriceDefined = self.safe_string(params, 'takeProfitPrice') is not None
        isTriggerOrder = self.safe_string(params, 'triggerPrice') is not None
        isType2Order = (isStopLossPriceDefined or isTakeProfitPriceDefined)
        response = None
        reduceOnly = self.safe_bool(params, 'reduceOnly')
        if reduceOnly is not None:
            params['reduceOnly'] = 'true' if reduceOnly else 'false'
        if tpsl or (method == 'privatePostTradeOrderTpsl') or isType2Order:
            tpslRequest = self.create_tpsl_order_request(symbol, type, side, amount, price, params)
            response = await self.privatePostTradeOrderTpsl(tpslRequest)
        elif isTriggerOrder or (method == 'privatePostTradeOrderAlgo'):
            triggerRequest = self.create_order_request(symbol, type, side, amount, price, params)
            response = await self.privatePostTradeOrderAlgo(triggerRequest)
        else:
            request = self.create_order_request(symbol, type, side, amount, price, params)
            response = await self.privatePostTradeOrder(request)
        if isTriggerOrder or (method == 'privatePostTradeOrderAlgo'):
            dataDict = self.safe_dict(response, 'data', {})
            triggerOrder = self.parse_order(dataDict, market)
            return triggerOrder
        data = self.safe_list(response, 'data', [])
        first = self.safe_dict(data, 0)
        order = self.parse_order(first, market)
        order['type'] = type
        order['side'] = side
        return order

    def create_tpsl_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
        market = self.market(symbol)
        positionSide = self.safe_string(params, 'positionSide', 'net')
        request: dict = {
            'instId': market['id'],
            'side': side,
            'positionSide': positionSide,
            'brokerId': self.safe_string(self.options, 'brokerId', 'ec6dd3a7dd982d0b'),
        }
        if amount is not None:
            request['size'] = self.amount_to_precision(symbol, amount)
        marginMode = self.safe_string(params, 'marginMode', 'cross')  # cross or isolated
        if marginMode != 'cross' and marginMode != 'isolated':
            raise BadRequest(self.id + ' createTpslOrder() requires a marginMode parameter that must be either cross or isolated')
        stopLossPrice = self.safe_string(params, 'stopLossPrice')
        takeProfitPrice = self.safe_string(params, 'takeProfitPrice')
        if stopLossPrice is not None:
            request['slTriggerPrice'] = self.price_to_precision(symbol, stopLossPrice)
            if type == 'market':
                request['slOrderPrice'] = '-1'
            else:
                request['slOrderPrice'] = self.price_to_precision(symbol, price)
        elif takeProfitPrice is not None:
            request['tpTriggerPrice'] = self.price_to_precision(symbol, takeProfitPrice)
            if type == 'market':
                request['tpOrderPrice'] = '-1'
            else:
                request['tpOrderPrice'] = self.price_to_precision(symbol, price)
        request['marginMode'] = marginMode
        params = self.omit(params, ['stopLossPrice', 'takeProfitPrice'])
        return self.extend(request, params)

    async def cancel_order(self, id: str, symbol: Str = None, params={}):
        """
        cancels an open order

        https://blofin.com/docs#cancel-order
        https://blofin.com/docs#cancel-tpsl-order

        :param str id: order id
        :param str symbol: unified symbol of the market the order was made in
        :param dict [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 dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'instId': market['id'],
        }
        isTrigger = self.safe_bool_n(params, ['trigger'], False)
        isTpsl = self.safe_bool_2(params, 'tpsl', 'TPSL', False)
        clientOrderId = self.safe_string(params, 'clientOrderId')
        if clientOrderId is not None:
            request['clientOrderId'] = clientOrderId
        else:
            if not isTrigger and not isTpsl:
                request['orderId'] = str(id)
            elif isTpsl:
                request['tpslId'] = str(id)
            elif isTrigger:
                request['algoId'] = str(id)
        query = self.omit(params, ['orderId', 'clientOrderId', 'stop', 'trigger', 'tpsl'])
        if isTpsl:
            tpslResponse = await self.cancel_orders([id], symbol, params)
            first = self.safe_dict(tpslResponse, 0)
            return first
        elif isTrigger:
            triggerResponse = await self.privatePostTradeCancelAlgo(self.extend(request, query))
            triggerData = self.safe_dict(triggerResponse, 'data')
            return self.parse_order(triggerData, market)
        response = await self.privatePostTradeCancelOrder(self.extend(request, query))
        data = self.safe_list(response, 'data', [])
        order = self.safe_dict(data, 0)
        return self.parse_order(order, market)

    async def create_orders(self, orders: List[OrderRequest], params={}) -> List[Order]:
        """
        create a list of trade orders

        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 dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        ordersRequests = []
        for i in range(0, len(orders)):
            rawOrder = orders[i]
            marketId = self.safe_string(rawOrder, 'symbol')
            type = self.safe_string(rawOrder, 'type')
            side = self.safe_string(rawOrder, 'side')
            amount = self.safe_value(rawOrder, 'amount')
            price = self.safe_value(rawOrder, 'price')
            orderParams = self.safe_dict(rawOrder, 'params', {})
            extendedParams = self.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
            orderRequest = self.create_order_request(marketId, type, side, amount, price, extendedParams)
            ordersRequests.append(orderRequest)
        response = await self.privatePostTradeBatchOrders(ordersRequests)
        data = self.safe_list(response, 'data', [])
        return self.parse_orders(data)

    async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        Fetch orders that are still open

        https://blofin.com/docs#get-active-orders
        https://blofin.com/docs#get-active-tpsl-orders
        https://docs.blofin.com/index.html#get-active-algo-orders

        :param str 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 dict [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 self 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 <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchOpenOrders', 'paginate')
        if paginate:
            return await self.fetch_paginated_call_dynamic('fetchOpenOrders', symbol, since, limit, params)
        request: dict = {
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['instId'] = market['id']
        if limit is not None:
            request['limit'] = limit  # default 100, max 100
        isTrigger = self.safe_bool_n(params, ['stop', 'trigger'], False)
        isTpSl = self.safe_bool_2(params, 'tpsl', 'TPSL', False)
        method: Str = None
        method, params = self.handle_option_and_params(params, 'fetchOpenOrders', 'method', 'privateGetTradeOrdersPending')
        query = self.omit(params, ['method', 'stop', 'trigger', 'tpsl', 'TPSL'])
        response = None
        if isTpSl or (method == 'privateGetTradeOrdersTpslPending'):
            response = await self.privateGetTradeOrdersTpslPending(self.extend(request, query))
        elif isTrigger or (method == 'privateGetTradeOrdersAlgoPending'):
            request['orderType'] = 'trigger'
            response = await self.privateGetTradeOrdersAlgoPending(self.extend(request, query))
        else:
            response = await self.privateGetTradeOrdersPending(self.extend(request, query))
        data = self.safe_list(response, 'data', [])
        return self.parse_orders(data, market, since, limit)

    async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        fetch all trades made by the user

        https://blofin.com/docs#get-trade-history

        :param str 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 dict [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 self 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 <https://docs.ccxt.com/#/?id=trade-structure>`
        """
        await self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchMyTrades', 'paginate')
        if paginate:
            return await self.fetch_paginated_call_dynamic('fetchMyTrades', symbol, since, limit, params)
        request: dict = {
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['instId'] = market['id']
        request, params = self.handle_until_option('end', request, params)
        if limit is not None:
            request['limit'] = limit  # default 100, max 100
        response = await self.privateGetTradeFillsHistory(self.extend(request, params))
        data = self.safe_list(response, 'data', [])
        return self.parse_trades(data, market, since, limit)

    async def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all deposits made to an account

        https://blofin.com/docs#get-deposite-history

        :param str 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 dict [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 self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
        :returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchDeposits', 'paginate')
        if paginate:
            return await self.fetch_paginated_call_dynamic('fetchDeposits', code, since, limit, params)
        request: dict = {
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        if since is not None:
            request['before'] = max(since - 1, 0)
        if limit is not None:
            request['limit'] = limit  # default 100, max 100
        request, params = self.handle_until_option('after', request, params)
        response = await self.privateGetAssetDepositHistory(self.extend(request, params))
        data = self.safe_list(response, 'data', [])
        return self.parse_transactions(data, currency, since, limit, params)

    async def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all withdrawals made from an account

        https://blofin.com/docs#get-withdraw-history

        :param str 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 dict [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 self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
        :returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchWithdrawals', 'paginate')
        if paginate:
            return await self.fetch_paginated_call_dynamic('fetchWithdrawals', code, since, limit, params)
        request: dict = {
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        if since is not None:
            request['before'] = max(since - 1, 0)
        if limit is not None:
            request['limit'] = limit  # default 100, max 100
        request, params = self.handle_until_option('after', request, params)
        response = await self.privateGetAssetWithdrawalHistory(self.extend(request, params))
        data = self.safe_list(response, 'data', [])
        return self.parse_transactions(data, currency, since, limit, params)

    async def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LedgerEntry]:
        """
        fetch the history of changes, actions done by the user or operations that altered the balance of the user

        https://blofin.com/docs#get-funds-transfer-history

        :param str [code]: unified currency code, default is None
        :param int [since]: timestamp in ms of the earliest ledger entry, default is None
        :param int [limit]: max number of ledger entries to return, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [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 self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
        :returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger>`
        """
        await self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchLedger', 'paginate')
        if paginate:
            return await self.fetch_paginated_call_dynamic('fetchLedger', code, since, limit, params)
        request: dict = {
        }
        if limit is not None:
            request['limit'] = limit
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        request, params = self.handle_until_option('end', request, params)
        response = None
        response = await self.privateGetAssetBills(self.extend(request, params))
        data = self.safe_list(response, 'data', [])
        return self.parse_ledger(data, currency, since, limit)

    def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
        #
        #
        # 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"
        #    }
        #
        type = None
        id = None
        withdrawalId = self.safe_string(transaction, 'withdrawId')
        depositId = self.safe_string(transaction, 'depositId')
        addressTo = self.safe_string(transaction, 'address')
        address = addressTo
        tagTo = self.safe_string(transaction, 'tag')
        if withdrawalId is not None:
            type = 'withdrawal'
            id = withdrawalId
        else:
            id = depositId
            type = 'deposit'
        currencyId = self.safe_string(transaction, 'currency')
        code = self.safe_currency_code(currencyId)
        amount = self.safe_number(transaction, 'amount')
        status = self.parse_transaction_status(self.safe_string(transaction, 'state'))
        txid = self.safe_string(transaction, 'txId')
        timestamp = self.safe_integer(transaction, 'ts')
        feeCurrencyId = self.safe_string(transaction, 'feeCurrency')
        feeCode = self.safe_currency_code(feeCurrencyId)
        feeCost = self.safe_number(transaction, 'fee')
        return {
            'info': transaction,
            'id': id,
            'currency': code,
            'amount': amount,
            'network': None,
            'addressFrom': None,
            'addressTo': addressTo,
            'address': address,
            'tagFrom': None,
            'tagTo': tagTo,
            'tag': tagTo,
            'status': status,
            'type': type,
            'updated': None,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'internal': None,
            'comment': None,
            'fee': {
                'currency': feeCode,
                'cost': feeCost,
            },
        }

    def parse_transaction_status(self, status: Str):
        statuses: dict = {
            '0': 'pending',
            '1': 'ok',
            '2': 'failed',
            '3': 'pending',
        }
        return self.safe_string(statuses, status, status)

    def parse_ledger_entry_type(self, type):
        types: dict = {
            '1': 'transfer',  # transfer
            '2': 'trade',  # trade
            '3': 'trade',  # delivery
            '4': 'rebate',  # auto token conversion
            '5': 'trade',  # liquidation
            '6': 'transfer',  # margin transfer
            '7': 'trade',  # interest deduction
            '8': 'fee',  # funding rate
            '9': 'trade',  # adl
            '10': 'trade',  # clawback
            '11': 'trade',  # system token conversion
        }
        return self.safe_string(types, type, type)

    def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry:
        currencyId = self.safe_string(item, 'currency')
        code = self.safe_currency_code(currencyId, currency)
        currency = self.safe_currency(currencyId, currency)
        timestamp = self.safe_integer(item, 'ts')
        return self.safe_ledger_entry({
            'info': item,
            'id': self.safe_string(item, 'transferId'),
            'direction': None,
            'account': None,
            'referenceId': self.safe_string(item, 'clientId'),
            'referenceAccount': None,
            'type': self.parse_ledger_entry_type(self.safe_string(item, 'type')),
            'currency': code,
            'amount': self.safe_number(item, 'amount'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'before': None,
            'after': None,
            'status': 'ok',
            'fee': None,
        }, currency)

    def parse_ids(self, ids):
        """
 @ignore
        :param string[]|str ids: order ids
        :returns str[]: list of order ids
        """
        if isinstance(ids, str):
            return ids.split(',')
        else:
            return ids

    async def cancel_orders(self, ids, symbol: Str = None, params={}):
        """
        cancel multiple orders

        https://blofin.com/docs#cancel-multiple-orders

        :param str[] ids: order ids
        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.trigger]: whether the order is a stop/trigger order
        :returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        # TODO : the original endpoint signature differs, according to that you can skip individual symbol and assign ids in batch. At self moment, `params` is not being used too.
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrders() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        request = []
        options = self.safe_dict(self.options, 'cancelOrders', {})
        defaultMethod = self.safe_string(options, 'method', 'privatePostTradeCancelBatchOrders')
        method = self.safe_string(params, 'method', defaultMethod)
        clientOrderIds = self.parse_ids(self.safe_value(params, 'clientOrderId'))
        tpslIds = self.parse_ids(self.safe_value(params, 'tpslId'))
        trigger = self.safe_bool_n(params, ['stop', 'trigger', 'tpsl'])
        if trigger:
            method = 'privatePostTradeCancelTpsl'
        if clientOrderIds is None:
            ids = self.parse_ids(ids)
            if tpslIds is not None:
                for i in range(0, len(tpslIds)):
                    request.append({
                        'tpslId': tpslIds[i],
                        'instId': market['id'],
                    })
            for i in range(0, len(ids)):
                if trigger:
                    request.append({
                        'tpslId': ids[i],
                        'instId': market['id'],
                    })
                else:
                    request.append({
                        'orderId': ids[i],
                        'instId': market['id'],
                    })
        else:
            for i in range(0, len(clientOrderIds)):
                request.append({
                    'instId': market['id'],
                    'clientOrderId': clientOrderIds[i],
                })
        response = None
        if method == 'privatePostTradeCancelTpsl':
            response = await self.privatePostTradeCancelTpsl(request)  # * dont self.extend with params, otherwise ARRAY will be turned into OBJECT
        else:
            response = await self.privatePostTradeCancelBatchOrders(request)  # * dont self.extend with params, otherwise ARRAY will be turned into OBJECT
        ordersData = self.safe_list(response, 'data', [])
        return self.parse_orders(ordersData, market, None, None, params)

    async def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
        """
        transfer currency internally between wallets on the same account

        https://blofin.com/docs#funds-transfer

        :param str code: unified currency code
        :param float amount: amount to transfer
        :param str fromAccount: account to transfer from(funding, swap, copy_trading, earn)
        :param str toAccount: account to transfer to(funding, swap, copy_trading, earn)
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
        """
        await self.load_markets()
        currency = self.currency(code)
        accountsByType = self.safe_dict(self.options, 'accountsByType', {})
        fromId = self.safe_string(accountsByType, fromAccount, fromAccount)
        toId = self.safe_string(accountsByType, toAccount, toAccount)
        request: dict = {
            'currency': currency['id'],
            'amount': self.currency_to_precision(code, amount),
            'fromAccount': fromId,
            'toAccount': toId,
        }
        response = await self.privatePostAssetTransfer(self.extend(request, params))
        data = self.safe_dict(response, 'data', {})
        return self.parse_transfer(data, currency)

    def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
        id = self.safe_string(transfer, 'transferId')
        return {
            'info': transfer,
            'id': id,
            'timestamp': None,
            'datetime': None,
            'currency': None,
            'amount': None,
            'fromAccount': None,
            'toAccount': None,
            'status': None,
        }

    async def fetch_position(self, symbol: str, params={}) -> Position:
        """
        fetch data on a single open contract trade position

        https://blofin.com/docs#get-positions

        :param str symbol: unified market symbol of the market the position is held in, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.instType]: MARGIN, SWAP, FUTURES, OPTION
        :returns dict: a `position structure <https://docs.ccxt.com/#/?id=position-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'instId': market['id'],
        }
        response = await self.privateGetAccountPositions(self.extend(request, params))
        data = self.safe_list(response, 'data', [])
        position = self.safe_dict(data, 0)
        if position is None:
            return None
        return self.parse_position(position, market)

    async def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
        """
        fetch data on a single open contract trade position

        https://blofin.com/docs#get-positions

        :param str[] [symbols]: list of unified market symbols
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.instType]: MARGIN, SWAP, FUTURES, OPTION
        :returns dict: a `position structure <https://docs.ccxt.com/#/?id=position-structure>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols)
        response = await self.privateGetAccountPositions(params)
        data = self.safe_list(response, 'data', [])
        result = self.parse_positions(data)
        return self.filter_by_array_positions(result, 'symbol', symbols, False)

    def parse_position(self, position: dict, market: Market = None):
        #
        # 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: '',  # self field might not exist in rest response
        #         marginRatio: '152.523509620342499273',
        #         maintenanceMargin: '0.34483156115',
        #         adl: '4',
        #         createTime: '1707235776528',
        #         updateTime: '1707235776528'
        #     }
        #
        marketId = self.safe_string(position, 'instId')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        pos = self.safe_string(position, 'positions')
        contractsAbs = Precise.string_abs(pos)
        side = self.safe_string(position, 'positionSide')
        hedged = side != 'net'
        contracts = self.parse_number(contractsAbs)
        if pos is not None:
            if side == 'net':
                if Precise.string_gt(pos, '0'):
                    side = 'long'
                elif Precise.string_lt(pos, '0'):
                    side = 'short'
                else:
                    side = None
        contractSize = self.safe_number(market, 'contractSize')
        contractSizeString = self.number_to_string(contractSize)
        markPriceString = self.safe_string(position, 'markPrice')
        notionalString = self.safe_string(position, 'notionalUsd')
        if market['inverse']:
            notionalString = Precise.string_div(Precise.string_mul(contractsAbs, contractSizeString), markPriceString)
        notional = self.parse_number(notionalString)
        marginMode = self.safe_string(position, 'marginMode')
        initialMarginString = None
        entryPriceString = self.safe_string(position, 'averagePrice')
        unrealizedPnlString = self.safe_string(position, 'unrealizedPnl')
        leverageString = self.safe_string(position, 'leverage')
        initialMarginPercentage = None
        collateralString = None
        if marginMode == 'cross':
            initialMarginString = self.safe_string(position, 'initialMargin')
            collateralString = Precise.string_add(initialMarginString, unrealizedPnlString)
        elif marginMode == 'isolated':
            initialMarginPercentage = Precise.string_div('1', leverageString)
            collateralString = self.safe_string(position, 'margin')
        maintenanceMarginString = self.safe_string(position, 'maintenanceMargin')
        maintenanceMargin = self.parse_number(maintenanceMarginString)
        maintenanceMarginPercentageString = Precise.string_div(maintenanceMarginString, notionalString)
        if initialMarginPercentage is None:
            initialMarginPercentage = self.parse_number(Precise.string_div(initialMarginString, notionalString, 4))
        elif initialMarginString is None:
            initialMarginString = Precise.string_mul(initialMarginPercentage, notionalString)
        rounder = '0.00005'  # round to closest 0.01%
        maintenanceMarginPercentage = self.parse_number(Precise.string_div(Precise.string_add(maintenanceMarginPercentageString, rounder), '1', 4))
        liquidationPrice = self.safe_number(position, 'liquidationPrice')
        percentageString = self.safe_string(position, 'unrealizedPnlRatio')
        percentage = self.parse_number(Precise.string_mul(percentageString, '100'))
        timestamp = self.safe_integer(position, 'updateTime')
        marginRatio = self.parse_number(Precise.string_div(maintenanceMarginString, collateralString, 4))
        return self.safe_position({
            'info': position,
            'id': None,
            'symbol': symbol,
            'notional': notional,
            'marginMode': marginMode,
            'liquidationPrice': liquidationPrice,
            'entryPrice': self.parse_number(entryPriceString),
            'unrealizedPnl': self.parse_number(unrealizedPnlString),
            'percentage': percentage,
            'contracts': contracts,
            'contractSize': contractSize,
            'markPrice': self.parse_number(markPriceString),
            'lastPrice': None,
            'side': side,
            'hedged': hedged,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastUpdateTimestamp': None,
            'maintenanceMargin': maintenanceMargin,
            'maintenanceMarginPercentage': maintenanceMarginPercentage,
            'collateral': self.parse_number(collateralString),
            'initialMargin': self.parse_number(initialMarginString),
            'initialMarginPercentage': self.parse_number(initialMarginPercentage),
            'leverage': self.parse_number(leverageString),
            'marginRatio': marginRatio,
            'stopLossPrice': None,
            'takeProfitPrice': None,
        })

    async def fetch_leverages(self, symbols: Strings = None, params={}) -> Leverages:
        """
        fetch the set leverage for all contract markets

        https://docs.blofin.com/index.html#get-multiple-leverage

        :param str[] symbols: a list of unified market symbols, required on blofin
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.marginMode]: 'cross' or 'isolated'
        :returns dict: a list of `leverage structures <https://docs.ccxt.com/#/?id=leverage-structure>`
        """
        await self.load_markets()
        if symbols is None:
            raise ArgumentsRequired(self.id + ' fetchLeverages() requires a symbols argument')
        marginMode = None
        marginMode, params = self.handle_margin_mode_and_params('fetchLeverages', params)
        if marginMode is None:
            marginMode = self.safe_string(params, 'marginMode', 'cross')  # cross marginMode
        if (marginMode != 'cross') and (marginMode != 'isolated'):
            raise BadRequest(self.id + ' fetchLeverages() requires a marginMode parameter that must be either cross or isolated')
        symbols = self.market_symbols(symbols)
        instIds = ''
        for i in range(0, len(symbols)):
            entry = symbols[i]
            entryMarket = self.market(entry)
            if i > 0:
                instIds = instIds + ',' + entryMarket['id']
            else:
                instIds = instIds + entryMarket['id']
        request: dict = {
            'instId': instIds,
            'marginMode': marginMode,
        }
        response = await self.privateGetAccountBatchLeverageInfo(self.extend(request, params))
        #
        #     {
        #         "code": "0",
        #         "msg": "success",
        #         "data": [
        #             {
        #                 "leverage": "3",
        #                 "marginMode": "cross",
        #                 "instId": "BTC-USDT"
        #             },
        #         ]
        #     }
        #
        leverages = self.safe_list(response, 'data', [])
        return self.parse_leverages(leverages, symbols, 'instId')

    async def fetch_leverage(self, symbol: str, params={}) -> Leverage:
        """
        fetch the set leverage for a market

        https://docs.blofin.com/index.html#get-leverage

        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.marginMode]: 'cross' or 'isolated'
        :returns dict: a `leverage structure <https://docs.ccxt.com/#/?id=leverage-structure>`
        """
        await self.load_markets()
        marginMode = None
        marginMode, params = self.handle_margin_mode_and_params('fetchLeverage', params)
        if marginMode is None:
            marginMode = self.safe_string(params, 'marginMode', 'cross')  # cross marginMode
        if (marginMode != 'cross') and (marginMode != 'isolated'):
            raise BadRequest(self.id + ' fetchLeverage() requires a marginMode parameter that must be either cross or isolated')
        market = self.market(symbol)
        request: dict = {
            'instId': market['id'],
            'marginMode': marginMode,
        }
        response = await self.privateGetAccountLeverageInfo(self.extend(request, params))
        #
        #     {
        #         "code": "0",
        #         "msg": "success",
        #         "data": {
        #             "leverage": "3",
        #             "marginMode": "cross",
        #             "instId": "BTC-USDT"
        #         }
        #     }
        #
        data = self.safe_dict(response, 'data', {})
        return self.parse_leverage(data, market)

    def parse_leverage(self, leverage: dict, market: Market = None) -> Leverage:
        marketId = self.safe_string(leverage, 'instId')
        leverageValue = self.safe_integer(leverage, 'leverage')
        return {
            'info': leverage,
            'symbol': self.safe_symbol(marketId, market),
            'marginMode': self.safe_string_lower(leverage, 'marginMode'),
            'longLeverage': leverageValue,
            'shortLeverage': leverageValue,
        }

    async def set_leverage(self, leverage: Int, symbol: Str = None, params={}):
        """
        set the level of leverage for a market

        https://blofin.com/docs#set-leverage

        :param int leverage: the rate of leverage
        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.marginMode]: 'cross' or 'isolated'
        :param str [params.positionSide]: 'long' or 'short' - required for hedged mode in isolated margin
        :returns dict: response from the exchange
        """
        if symbol is None:
            raise ArgumentsRequired(self.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) or (leverage > 125):
            raise BadRequest(self.id + ' setLeverage() leverage should be between 1 and 125')
        await self.load_markets()
        market = self.market(symbol)
        marginMode = None
        marginMode, params = self.handle_margin_mode_and_params('setLeverage', params, 'cross')
        if (marginMode != 'cross') and (marginMode != 'isolated'):
            raise BadRequest(self.id + ' setLeverage() requires a marginMode parameter that must be either cross or isolated')
        request: dict = {
            'leverage': leverage,
            'marginMode': marginMode,
            'instId': market['id'],
        }
        response = await self.privatePostAccountSetLeverage(self.extend(request, params))
        return response

    async def close_position(self, symbol: str, side: OrderSide = None, params={}) -> Order:
        """
        closes open positions for a market

        https://blofin.com/docs#close-positions

        :param str symbol: Unified CCXT market symbol
        :param str [side]: 'buy' or 'sell', leave in net mode
        :param dict [params]: extra parameters specific to the blofin api endpoint
        :param str [params.clientOrderId]: a unique identifier for the order
        :param str [params.marginMode]: 'cross' or 'isolated', default is 'cross
        :param str [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 str [params.tag]: order tag a combination of case-sensitive alphanumerics, all numbers, or all letters of up to 16 characters
        :returns dict[]: `A list of position structures <https://docs.ccxt.com/#/?id=position-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        clientOrderId = self.safe_string(params, 'clientOrderId')
        marginMode = None
        marginMode, params = self.handle_margin_mode_and_params('closePosition', params, 'cross')
        request: dict = {
            'instId': market['id'],
            'marginMode': marginMode,
        }
        if clientOrderId is not None:
            request['clientOrderId'] = clientOrderId
        response = await self.privatePostTradeClosePosition(self.extend(request, params))
        return self.safe_dict(response, 'data')

    async def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetches information on multiple closed orders made by the user

        https://blofin.com/docs#get-order-history
        https://blofin.com/docs#get-tpsl-order-history

        :param str 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 dict [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 self 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 <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchClosedOrders', 'paginate')
        if paginate:
            return await self.fetch_paginated_call_dynamic('fetchClosedOrders', symbol, since, limit, params)
        request: dict = {
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['instId'] = market['id']
        if limit is not None:
            request['limit'] = limit  # default 100, max 100
        if since is not None:
            request['begin'] = since
        isTrigger = self.safe_bool_n(params, ['stop', 'trigger', 'tpsl', 'TPSL'], False)
        method: Str = None
        method, params = self.handle_option_and_params(params, 'fetchOpenOrders', 'method', 'privateGetTradeOrdersHistory')
        query = self.omit(params, ['method', 'stop', 'trigger', 'tpsl', 'TPSL'])
        response = None
        if (isTrigger) or (method == 'privateGetTradeOrdersTpslHistory'):
            response = await self.privateGetTradeOrdersTpslHistory(self.extend(request, query))
        else:
            response = await self.privateGetTradeOrdersHistory(self.extend(request, query))
        data = self.safe_list(response, 'data', [])
        return self.parse_orders(data, market, since, limit)

    async def fetch_margin_mode(self, symbol: str, params={}) -> MarginMode:
        """
        fetches the margin mode of a trading pair

        https://docs.blofin.com/index.html#get-margin-mode

        :param str symbol: unified symbol of the market to fetch the margin mode for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `margin mode structure <https://docs.ccxt.com/#/?id=margin-mode-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        response = await self.privateGetAccountMarginMode(params)
        #
        #     {
        #         "code": "0",
        #         "msg": "success",
        #         "data": {
        #             "marginMode": "cross"
        #         }
        #     }
        #
        data = self.safe_dict(response, 'data', {})
        return self.parse_margin_mode(data, market)

    def parse_margin_mode(self, marginMode: dict, market: Market = None) -> MarginMode:
        return {
            'info': marginMode,
            'symbol': self.safe_string(market, 'symbol'),
            'marginMode': self.safe_string(marginMode, 'marginMode'),
        }

    async def set_margin_mode(self, marginMode: str, symbol: Str = None, params={}):
        """
        set margin mode to 'cross' or 'isolated'

        https://docs.blofin.com/index.html#set-margin-mode

        :param str marginMode: 'cross' or 'isolated'
        :param str [symbol]: unified market symbol(not used in blofin setMarginMode)
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: response from the exchange
        """
        self.check_required_argument('setMarginMode', marginMode, 'marginMode', ['cross', 'isolated'])
        await self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        request: dict = {
            'marginMode': marginMode,
        }
        response = await self.privatePostAccountSetMarginMode(self.extend(request, params))
        #
        #     {
        #         "code": "0",
        #         "msg": "success",
        #         "data": {
        #             "marginMode": "isolated"
        #         }
        #     }
        #
        data = self.safe_dict(response, 'data', {})
        return self.parse_margin_mode(data, market)

    async def fetch_position_mode(self, symbol: Str = None, params={}):
        """
        fetchs the position mode, hedged or one way

        https://docs.blofin.com/index.html#get-position-mode

        :param str [symbol]: unified symbol of the market to fetch the position mode for(not used in blofin fetchPositionMode)
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an object detailing whether the market is in hedged or one-way mode
        """
        response = await self.privateGetAccountPositionMode(params)
        data = self.safe_dict(response, 'data', {})
        positionMode = self.safe_string(data, 'positionMode')
        #
        #     {
        #         "code": "0",
        #         "msg": "success",
        #         "data": {
        #             "positionMode": "long_short_mode"
        #         }
        #     }
        #
        return {
            'info': data,
            'hedged': positionMode == 'long_short_mode',
        }

    async def set_position_mode(self, hedged: bool, symbol: Str = None, params={}):
        """
        set hedged to True or False for a market

        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 str [symbol]: not used by blofin setPositionMode()
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: response from the exchange
        """
        request: dict = {
            'positionMode': 'long_short_mode' if hedged else 'net_mode',
        }
        #
        #     {
        #         "code": "0",
        #         "msg": "success",
        #         "data": {
        #             "positionMode": "net_mode"
        #         }
        #     }
        #
        return await self.privatePostAccountSetPositionMode(self.extend(request, params))

    def handle_errors(self, httpCode: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
        if response is None:
            return None  # fallback to default error handler
        #
        # {"code":"152002","msg":"Parameter bar error."}
        #
        code = self.safe_string(response, 'code')
        message = self.safe_string(response, 'msg')
        feedback = self.id + ' ' + body
        if code is not None and code != '0':
            self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
            self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
            raise ExchangeError(feedback)  # unknown message
        #
        #  {
        #      orderId: null,
        #      clientOrderId: '',
        #      msg: 'Order failed. Insufficient USDT margin in account',
        #      code: '103003'
        #  }
        #
        data = self.safe_list(response, 'data')
        first = self.safe_dict(data, 0)
        insideMsg = self.safe_string(first, 'msg')
        insideCode = self.safe_string(first, 'code')
        if insideCode is not None and insideCode != '0':
            self.throw_exactly_matched_exception(self.exceptions['exact'], insideCode, feedback)
            self.throw_exactly_matched_exception(self.exceptions['exact'], insideMsg, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], insideMsg, feedback)
        return None

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        request = '/api/' + self.version + '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        url = self.implode_hostname(self.urls['api']['rest']) + request
        # type = self.getPathAuthenticationType(path)
        if api == 'public':
            if not self.is_empty(query):
                url += '?' + self.urlencode(query)
        elif api == 'private':
            self.check_required_credentials()
            timestamp = str(self.milliseconds())
            headers = {
                'ACCESS-KEY': self.apiKey,
                'ACCESS-PASSPHRASE': self.password,
                'ACCESS-TIMESTAMP': timestamp,
                'ACCESS-NONCE': timestamp,
            }
            sign_body = ''
            if method == 'GET':
                if not self.is_empty(query):
                    urlencodedQuery = '?' + self.urlencode(query)
                    url += urlencodedQuery
                    request += urlencodedQuery
            else:
                if not self.is_empty(query):
                    body = self.json(query)
                    sign_body = body
                headers['Content-Type'] = 'application/json'
            auth = request + method + timestamp + timestamp + sign_body
            signature = self.string_to_base64(self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256))
            headers['ACCESS-SIGN'] = signature
        return {'url': url, 'method': method, 'body': body, 'headers': headers}
