# -*- 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
import hashlib
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import AccountSuspended
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidAddress
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import CancelPending
from ccxt.base.errors import NotSupported
from ccxt.base.errors import NetworkError
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.errors import OnMaintenance
from ccxt.base.errors import InvalidNonce
from ccxt.base.errors import RequestTimeout
from ccxt.base.decimal_to_precision import TRUNCATE
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class okex(Exchange):

    def describe(self):
        return self.deep_extend(super(okex, self).describe(), {
            'id': 'okex',
            'name': 'OKEX',
            'countries': ['CN', 'US'],
            'version': 'v5',
            'rateLimit': 100,
            'pro': True,
            'certified': True,
            'has': {
                'margin': True,
                'swap': True,
                'future': True,
                'addMargin': True,
                'cancelAllOrders': None,
                'cancelOrder': True,
                'cancelOrders': None,
                'CORS': None,
                'createDepositAddress': None,
                'createOrder': True,
                'createReduceOnlyOrder': None,
                'deposit': None,
                'fetchAccounts': None,
                'fetchAllTradingFees': None,
                'fetchBalance': True,
                'fetchBidsAsks': None,
                'fetchBorrowRate': True,
                'fetchBorrowRateHistory': None,
                'fetchBorrowRates': True,
                'fetchBorrowRatesPerSymbol': False,
                'fetchCanceledOrders': None,
                'fetchClosedOrder': None,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDeposit': None,
                'fetchDepositAddress': True,
                'fetchDepositAddresses': None,
                'fetchDepositAddressesByNetwork': True,
                'fetchDeposits': True,
                'fetchFundingFee': None,
                'fetchFundingFees': None,
                'fetchFundingHistory': True,
                'fetchFundingRate': True,
                'fetchFundingRateHistory': True,
                'fetchFundingRates': None,
                'fetchIndexOHLCV': True,
                'fetchIsolatedPositions': None,
                'fetchL3OrderBook': None,
                'fetchLedger': True,
                'fetchLedgerEntry': None,
                'fetchLeverage': True,
                'fetchMarkets': True,
                'fetchMarketsByType': True,
                'fetchMarkOHLCV': True,
                'fetchMyBuys': None,
                'fetchMySells': None,
                'fetchMyTrades': True,
                'fetchNetworkDepositAddress': None,
                'fetchOHLCV': True,
                'fetchOpenOrder': None,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrderBooks': None,
                'fetchOrders': None,
                'fetchOrdersByState': None,
                'fetchOrdersByStatus': None,
                'fetchOrderTrades': True,
                'fetchPartiallyFilledOrders': None,
                'fetchPosition': True,
                'fetchPositions': True,
                'fetchPositionsRisk': None,
                'fetchPremiumIndexOHLCV': None,
                'fetchStatus': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTickersByType': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTradingFee': True,
                'fetchTradingFees': None,
                'fetchTradingLimits': None,
                'fetchTransactions': None,
                'fetchTransfers': None,
                'fetchWithdrawAddress': None,
                'fetchWithdrawAddressesByNetwork': None,
                'fetchWithdrawal': None,
                'fetchWithdrawals': True,
                'fetchWithdrawalWhitelist': None,
                'loadLeverageBrackets': None,
                'reduceMargin': True,
                'setLeverage': True,
                'setMarginMode': True,
                'setPositionMode': True,
                'signIn': None,
                'transfer': True,
                'transferOut': False,
                'withdraw': True,
            },
            'timeframes': {
                '1m': '1m',
                '3m': '3m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1H',
                '2h': '2H',
                '4h': '4H',
                '6h': '6Hutc',
                '12h': '12Hutc',
                '1d': '1Dutc',
                '1w': '1Wutc',
                '1M': '1Mutc',
                '3M': '3Mutc',
                '6M': '6Mutc',
                '1y': '1Yutc',
            },
            'hostname': 'www.okex.com',
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/32552768-0d6dd3c6-c4a6-11e7-90f8-c043b64756a7.jpg',
                'api': {
                    'rest': 'https://{hostname}',
                },
                'www': 'https://www.okex.com',
                'doc': 'https://www.okex.com/docs-v5/en/',
                'fees': 'https://www.okex.com/pages/products/fees.html',
                # 'referral': 'https://www.okex.com/join/1888677',
                'test': {
                    'rest': 'https://testnet.okex.com',
                },
            },
            'api': {
                'public': {
                    'get': {
                        'market/tickers': 1,
                        'market/ticker': 1,
                        'market/index-tickers': 1,
                        'market/books': 1,
                        'market/candles': 1,
                        'market/history-candles': 1,
                        'market/index-candles': 1,
                        'market/mark-price-candles': 1,
                        'market/trades': 1,
                        'market/platform-24-volume': 10,
                        'market/open-oracle': 100,
                        'market/index-components': 1,
                        # 'market/oracle',
                        'public/instruments': 1,
                        'public/delivery-exercise-history': 0.5,
                        'public/open-interest': 1,
                        'public/funding-rate': 1,
                        'public/funding-rate-history': 1,
                        'public/price-limit': 1,
                        'public/opt-summary': 1,
                        'public/estimated-price': 2,
                        'public/discount-rate-interest-free-quota': 10,
                        'public/time': 2,
                        'public/liquidation-orders': 0.5,
                        'public/mark-price': 2,
                        # 'public/tier',
                        'public/position-tiers': 2,
                        'public/underlying': 1,
                        'public/interest-rate-loan-quota': 10,
                        'rubik/stat/trading-data/support-coin': 4,
                        'rubik/stat/taker-volume': 4,
                        'rubik/stat/margin/loan-ratio': 4,
                        # long/short
                        'rubik/stat/contracts/long-short-account-ratio': 4,
                        'rubik/stat/contracts/open-interest-volume': 4,
                        'rubik/stat/option/open-interest-volume': 4,
                        # put/call
                        'rubik/stat/option/open-interest-volume-ratio': 4,
                        'rubik/stat/option/open-interest-volume-expiry': 4,
                        'rubik/stat/option/open-interest-volume-strike': 4,
                        'rubik/stat/option/taker-block-volume': 4,
                        'system/status': 100,
                    },
                },
                'private': {
                    'get': {
                        'account/account-position-risk': 2,
                        'account/balance': 2,
                        'account/positions': 2,
                        'account/bills': 5 / 3,
                        'account/bills-archive': 5 / 3,
                        'account/config': 4,
                        'account/max-size': 1,
                        'account/max-avail-size': 1,
                        'account/leverage-info': 1,
                        'account/max-loan': 1,
                        'account/trade-fee': 4,
                        'account/interest-accrued': 4,
                        'account/interest-rate': 4,
                        'account/max-withdrawal': 1,
                        'asset/asset-valuation': 1 / 5,
                        'asset/deposit-address': 5 / 3,
                        'asset/balances': 5 / 3,
                        'asset/transfer-state': 10,
                        'asset/deposit-history': 5 / 3,
                        'asset/withdrawal-history': 5 / 3,
                        'asset/currencies': 5 / 3,
                        'asset/bills': 5 / 3,
                        'asset/piggy-balance': 5 / 3,
                        'asset/deposit-lightning': 5,
                        'trade/order': 1 / 3,
                        'trade/orders-pending': 1,
                        'trade/orders-history': 0.5,
                        'trade/orders-history-archive': 1,
                        'trade/fills': 1 / 3,
                        'trade/fills-history': 2,
                        'trade/orders-algo-pending': 1,
                        'trade/orders-algo-history': 1,
                        'account/subaccount/balances': 10,
                        'asset/subaccount/bills': 5 / 3,
                        'users/subaccount/list': 10,
                        'users/subaccount/apikey': 10,
                    },
                    'post': {
                        'account/set-position-mode': 4,
                        'account/set-leverage': 1,
                        'account/position/margin-balance': 1,
                        'account/set-greeks': 4,
                        'asset/transfer': 10,
                        'asset/withdrawal': 5 / 3,
                        'asset/purchase_redempt': 5 / 3,
                        'asset/withdrawal-lightning': 5,
                        'trade/order': 1 / 3,
                        'trade/batch-orders': 1 / 15,
                        'trade/cancel-order': 1 / 3,
                        'trade/cancel-batch-orders': 1 / 15,
                        'trade/amend-order': 1 / 3,
                        'trade/amend-batch-orders': 1 / 3,
                        'trade/close-position': 1,
                        'trade/order-algo': 1,
                        'trade/cancel-algos': 1,
                        'trade/cancel-advance-algos': 1,
                        'users/subaccount/delete-apikey': 10,
                        'users/subaccount/modify-apikey': 10,
                        'users/subaccount/apikey': 10,
                        'asset/subaccount/transfer': 10,
                    },
                },
            },
            'fees': {
                'trading': {
                    'taker': self.parse_number('0.0015'),
                    'maker': self.parse_number('0.0010'),
                },
                'spot': {
                    'taker': self.parse_number('0.0015'),
                    'maker': self.parse_number('0.0010'),
                },
                'futures': {
                    'taker': self.parse_number('0.0005'),
                    'maker': self.parse_number('0.0002'),
                },
                'swap': {
                    'taker': self.parse_number('0.00050'),
                    'maker': self.parse_number('0.00020'),
                },
            },
            'requiredCredentials': {
                'apiKey': True,
                'secret': True,
                'password': True,
            },
            'exceptions': {
                'exact': {
                    # Public error codes from 50000-53999
                    # General Class
                    '1': ExchangeError,  # Operation failed
                    '2': ExchangeError,  # Bulk operation partially succeeded
                    '50000': BadRequest,  # Body can not be empty
                    '50001': OnMaintenance,  # Matching engine upgrading. Please try again later
                    '50002': BadRequest,  # Json data format error
                    '50004': RequestTimeout,  # Endpoint request timeout(does not indicate success or failure of order, please check order status)
                    '50005': ExchangeNotAvailable,  # API is offline or unavailable
                    '50006': BadRequest,  # Invalid Content_Type, please use "application/json" format
                    '50007': AccountSuspended,  # Account blocked
                    '50008': AuthenticationError,  # User does not exist
                    '50009': AccountSuspended,  # Account is suspended due to ongoing liquidation
                    '50010': ExchangeError,  # User ID can not be empty
                    '50011': RateLimitExceeded,  # Request too frequent
                    '50012': ExchangeError,  # Account status invalid
                    '50013': ExchangeNotAvailable,  # System is busy, please try again later
                    '50014': ExchangeError,  # Parameter {0} can not be empty
                    '50015': ExchangeError,  # Either parameter {0} or {1} is required
                    '50016': ExchangeError,  # Parameter {0} does not match parameter {1}
                    '50017': ExchangeError,  # The position is frozen due to ADL. Operation restricted
                    '50018': ExchangeError,  # Currency {0} is frozen due to ADL. Operation restricted
                    '50019': ExchangeError,  # The account is frozen due to ADL. Operation restricted
                    '50020': ExchangeError,  # The position is frozen due to liquidation. Operation restricted
                    '50021': ExchangeError,  # Currency {0} is frozen due to liquidation. Operation restricted
                    '50022': ExchangeError,  # The account is frozen due to liquidation. Operation restricted
                    '50023': ExchangeError,  # Funding fee frozen. Operation restricted
                    '50024': ExchangeError,  # Parameter {0} and {1} can not exist at the same time
                    '50025': ExchangeError,  # Parameter {0} count exceeds the limit {1}
                    '50026': ExchangeError,  # System error
                    '50027': ExchangeError,  # The account is restricted from trading
                    '50028': ExchangeError,  # Unable to take the order, please reach out to support center for details
                    # API Class
                    '50100': ExchangeError,  # API frozen, please contact customer service
                    '50101': ExchangeError,  # Broker id of APIKey does not match current environment
                    '50102': InvalidNonce,  # Timestamp request expired
                    '50103': AuthenticationError,  # Request header "OK_ACCESS_KEY" can not be empty
                    '50104': AuthenticationError,  # Request header "OK_ACCESS_PASSPHRASE" can not be empty
                    '50105': AuthenticationError,  # Request header "OK_ACCESS_PASSPHRASE" incorrect
                    '50106': AuthenticationError,  # Request header "OK_ACCESS_SIGN" can not be empty
                    '50107': AuthenticationError,  # Request header "OK_ACCESS_TIMESTAMP" can not be empty
                    '50108': ExchangeError,  # Exchange ID does not exist
                    '50109': ExchangeError,  # Exchange domain does not exist
                    '50110': PermissionDenied,  # Invalid IP
                    '50111': AuthenticationError,  # Invalid OK_ACCESS_KEY
                    '50112': AuthenticationError,  # Invalid OK_ACCESS_TIMESTAMP
                    '50113': AuthenticationError,  # Invalid signature
                    '50114': AuthenticationError,  # Invalid authorization
                    '50115': BadRequest,  # Invalid request method
                    # Trade Class
                    '51000': BadRequest,  # Parameter {0} error
                    '51001': BadSymbol,  # Instrument ID does not exist
                    '51002': BadSymbol,  # Instrument ID does not match underlying index
                    '51003': BadRequest,  # Either client order ID or order ID is required
                    '51004': InvalidOrder,  # Order amount exceeds current tier limit
                    '51005': InvalidOrder,  # Order amount exceeds the limit
                    '51006': InvalidOrder,  # Order price out of the limit
                    '51007': InvalidOrder,  # Order placement failed. Order amount should be at least 1 contract(showing up when placing an order with less than 1 contract)
                    '51008': InsufficientFunds,  # Order placement failed due to insufficient balance
                    '51009': AccountSuspended,  # Order placement function is blocked by the platform
                    '51010': InsufficientFunds,  # Account level too low
                    '51011': InvalidOrder,  # Duplicated order ID
                    '51012': ExchangeError,  # Token does not exist
                    '51014': ExchangeError,  # Index does not exist
                    '51015': BadSymbol,  # Instrument ID does not match instrument type
                    '51016': InvalidOrder,  # Duplicated client order ID
                    '51017': ExchangeError,  # Borrow amount exceeds the limit
                    '51018': ExchangeError,  # User with option account can not hold net short positions
                    '51019': ExchangeError,  # No net long positions can be held under isolated margin mode in options
                    '51020': InvalidOrder,  # Order amount should be greater than the min available amount
                    '51021': BadSymbol,  # Contract to be listed
                    '51022': BadSymbol,  # Contract suspended
                    '51023': ExchangeError,  # Position does not exist
                    '51024': AccountSuspended,  # Unified accountblocked
                    '51025': ExchangeError,  # Order count exceeds the limit
                    '51026': BadSymbol,  # Instrument type does not match underlying index
                    '51027': BadSymbol,  # Contract expired
                    '51028': BadSymbol,  # Contract under delivery
                    '51029': BadSymbol,  # Contract is being settled
                    '51030': BadSymbol,  # Funding fee is being settled
                    '51031': InvalidOrder,  # This order price is not within the closing price range
                    '51100': InvalidOrder,  # Trading amount does not meet the min tradable amount
                    '51101': InvalidOrder,  # Entered amount exceeds the max pending order amount(Cont) per transaction
                    '51102': InvalidOrder,  # Entered amount exceeds the max pending count
                    '51103': InvalidOrder,  # Entered amount exceeds the max pending order count of the underlying asset
                    '51104': InvalidOrder,  # Entered amount exceeds the max pending order amount(Cont) of the underlying asset
                    '51105': InvalidOrder,  # Entered amount exceeds the max order amount(Cont) of the contract
                    '51106': InvalidOrder,  # Entered amount exceeds the max order amount(Cont) of the underlying asset
                    '51107': InvalidOrder,  # Entered amount exceeds the max holding amount(Cont)
                    '51108': InvalidOrder,  # Positions exceed the limit for closing out with the market price
                    '51109': InvalidOrder,  # No available offer
                    '51110': InvalidOrder,  # You can only place a limit order after Call Auction has started
                    '51111': BadRequest,  # Maximum {0} orders can be placed in bulk
                    '51112': InvalidOrder,  # Close order size exceeds your available size
                    '51113': RateLimitExceeded,  # Market-price liquidation requests too frequent
                    '51115': InvalidOrder,  # Cancel all pending close-orders before liquidation
                    '51116': InvalidOrder,  # Order price or trigger price exceeds {0}
                    '51117': InvalidOrder,  # Pending close-orders count exceeds limit
                    '51118': InvalidOrder,  # Total amount should exceed the min amount per order
                    '51119': InsufficientFunds,  # Order placement failed due to insufficient balance
                    '51120': InvalidOrder,  # Order quantity is less than {0}, please try again
                    '51121': InvalidOrder,  # Order count should be the integer multiples of the lot size
                    '51122': InvalidOrder,  # Order price should be higher than the min price {0}
                    '51124': InvalidOrder,  # You can only place limit orders during call auction
                    '51125': InvalidOrder,  # Currently there are reduce + reverse position pending orders in margin trading. Please cancel all reduce + reverse position pending orders and continue
                    '51126': InvalidOrder,  # Currently there are reduce only pending orders in margin trading.Please cancel all reduce only pending orders and continue
                    '51127': InsufficientFunds,  # Available balance is 0
                    '51128': InvalidOrder,  # Multi-currency margin account can not do cross-margin trading
                    '51129': InvalidOrder,  # The value of the position and buy order has reached the position limit, and no further buying is allowed
                    '51130': BadSymbol,  # Fixed margin currency error
                    '51131': InsufficientFunds,  # Insufficient balance
                    '51132': InvalidOrder,  # Your position amount is negative and less than the minimum trading amount
                    '51133': InvalidOrder,  # Reduce-only feature is unavailable for the spot transactions by multi-currency margin account
                    '51134': InvalidOrder,  # Closing failed. Please check your holdings and pending orders
                    '51135': InvalidOrder,  # Your closing price has triggered the limit price, and the max buy price is {0}
                    '51136': InvalidOrder,  # Your closing price has triggered the limit price, and the min sell price is {0}
                    '51137': InvalidOrder,  # Your opening price has triggered the limit price, and the max buy price is {0}
                    '51138': InvalidOrder,  # Your opening price has triggered the limit price, and the min sell price is {0}
                    '51139': InvalidOrder,  # Reduce-only feature is unavailable for the spot transactions by simple account
                    '51201': InvalidOrder,  # Value of per market order cannot exceed 100,000 USDT
                    '51202': InvalidOrder,  # Market - order amount exceeds the max amount
                    '51203': InvalidOrder,  # Order amount exceeds the limit {0}
                    '51204': InvalidOrder,  # The price for the limit order can not be empty
                    '51205': InvalidOrder,  # Reduce-Only is not available
                    '51250': InvalidOrder,  # Algo order price is out of the available range
                    '51251': InvalidOrder,  # Algo order type error(when user place an iceberg order)
                    '51252': InvalidOrder,  # Algo order price is out of the available range
                    '51253': InvalidOrder,  # Average amount exceeds the limit of per iceberg order
                    '51254': InvalidOrder,  # Iceberg average amount error(when user place an iceberg order)
                    '51255': InvalidOrder,  # Limit of per iceberg order: Total amount/1000 < x <= Total amount
                    '51256': InvalidOrder,  # Iceberg order price variance error
                    '51257': InvalidOrder,  # Trail order callback rate error
                    '51258': InvalidOrder,  # Trail - order placement failed. The trigger price of a sell order should be higher than the last transaction price
                    '51259': InvalidOrder,  # Trail - order placement failed. The trigger price of a buy order should be lower than the last transaction price
                    '51260': InvalidOrder,  # Maximum {0} pending trail - orders can be held at the same time
                    '51261': InvalidOrder,  # Each user can hold up to {0} pending stop - orders at the same time
                    '51262': InvalidOrder,  # Maximum {0} pending iceberg orders can be held at the same time
                    '51263': InvalidOrder,  # Maximum {0} pending time-weighted orders can be held at the same time
                    '51264': InvalidOrder,  # Average amount exceeds the limit of per time-weighted order
                    '51265': InvalidOrder,  # Time-weighted order limit error
                    '51267': InvalidOrder,  # Time-weighted order strategy initiative rate error
                    '51268': InvalidOrder,  # Time-weighted order strategy initiative range error
                    '51269': InvalidOrder,  # Time-weighted order interval error, the interval should be {0}<= x<={1}
                    '51270': InvalidOrder,  # The limit of time-weighted order price variance is 0 < x <= 1%
                    '51271': InvalidOrder,  # Sweep ratio should be 0 < x <= 100%
                    '51272': InvalidOrder,  # Price variance should be 0 < x <= 1%
                    '51273': InvalidOrder,  # Total amount should be more than {0}
                    '51274': InvalidOrder,  # Total quantity of time-weighted order must be larger than single order limit
                    '51275': InvalidOrder,  # The amount of single stop-market order can not exceed the upper limit
                    '51276': InvalidOrder,  # Stop - Market orders cannot specify a price
                    '51277': InvalidOrder,  # TP trigger price can not be higher than the last price
                    '51278': InvalidOrder,  # SL trigger price can not be lower than the last price
                    '51279': InvalidOrder,  # TP trigger price can not be lower than the last price
                    '51280': InvalidOrder,  # SL trigger price can not be higher than the last price
                    '51400': OrderNotFound,  # Cancellation failed as the order does not exist
                    '51401': OrderNotFound,  # Cancellation failed as the order is already canceled
                    '51402': OrderNotFound,  # Cancellation failed as the order is already completed
                    '51403': InvalidOrder,  # Cancellation failed as the order type does not support cancellation
                    '51404': InvalidOrder,  # Order cancellation unavailable during the second phase of call auction
                    '51405': ExchangeError,  # Cancellation failed as you do not have any pending orders
                    '51406': ExchangeError,  # Canceled - order count exceeds the limit {0}
                    '51407': BadRequest,  # Either order ID or client order ID is required
                    '51408': ExchangeError,  # Pair ID or name does not match the order info
                    '51409': ExchangeError,  # Either pair ID or pair name ID is required
                    '51410': CancelPending,  # Cancellation failed as the order is already under cancelling status
                    '51500': ExchangeError,  # Either order price or amount is required
                    '51501': ExchangeError,  # Maximum {0} orders can be modified
                    '51502': InsufficientFunds,  # Order modification failed for insufficient margin
                    '51503': ExchangeError,  # Order modification failed as the order does not exist
                    '51506': ExchangeError,  # Order modification unavailable for the order type
                    '51508': ExchangeError,  # Orders are not allowed to be modified during the call auction
                    '51509': ExchangeError,  # Modification failed as the order has been canceled
                    '51510': ExchangeError,  # Modification failed as the order has been completed
                    '51511': ExchangeError,  # Modification failed as the order price did not meet the requirement for Post Only
                    '51600': ExchangeError,  # Status not found
                    '51601': ExchangeError,  # Order status and order ID cannot exist at the same time
                    '51602': ExchangeError,  # Either order status or order ID is required
                    '51603': OrderNotFound,  # Order does not exist
                    # Data class
                    '52000': ExchangeError,  # No updates
                    # SPOT/MARGIN error codes 54000-54999
                    '54000': ExchangeError,  # Margin transactions unavailable
                    '54001': ExchangeError,  # Only Multi-currency margin account can be set to borrow coins automatically
                    # FUNDING error codes 58000-58999
                    '58000': ExchangeError,  # Account type {0} does not supported when getting the sub-account balance
                    '58001': AuthenticationError,  # Incorrect trade password
                    '58002': PermissionDenied,  # Please activate Savings Account first
                    '58003': ExchangeError,  # Currency type is not supported by Savings Account
                    '58004': AccountSuspended,  # Account blocked(transfer & withdrawal endpoint: either end of the account does not authorize the transfer)
                    '58005': ExchangeError,  # The redeemed amount must be no greater than {0}
                    '58006': ExchangeError,  # Service unavailable for token {0}
                    '58007': ExchangeError,  # Abnormal Assets interface. Please try again later
                    '58100': ExchangeError,  # The trading product triggers risk control, and the platform has suspended the fund transfer-out function with related users. Please wait patiently
                    '58101': AccountSuspended,  # Transfer suspended(transfer endpoint: either end of the account does not authorize the transfer)
                    '58102': RateLimitExceeded,  # Too frequent transfer(transfer too frequently)
                    '58103': ExchangeError,  # Parent account user id does not match sub-account user id
                    '58104': ExchangeError,  # Since your P2P transaction is abnormal, you are restricted from making fund transfers. Please contact customer support to remove the restriction
                    '58105': ExchangeError,  # Since your P2P transaction is abnormal, you are restricted from making fund transfers. Please transfer funds on our website or app to complete identity verification
                    '58106': ExchangeError,  # Please enable the account for spot contract
                    '58107': ExchangeError,  # Please enable the account for futures contract
                    '58108': ExchangeError,  # Please enable the account for option contract
                    '58109': ExchangeError,  # Please enable the account for swap contract
                    '58110': ExchangeError,  # The contract triggers risk control, and the platform has suspended the fund transfer function of it. Please wait patiently
                    '58111': ExchangeError,  # Funds transfer unavailable as the perpetual contract is charging the funding fee. Please try again later
                    '58112': ExchangeError,  # Your fund transfer failed. Please try again later
                    '58114': ExchangeError,  # Transfer amount must be more than 0
                    '58115': ExchangeError,  # Sub-account does not exist
                    '58116': ExchangeError,  # Transfer amount exceeds the limit
                    '58117': ExchangeError,  # Account assets are abnormal, please deal with negative assets before transferring
                    '58200': ExchangeError,  # Withdrawal from {0} to {1} is unavailable for self currency
                    '58201': ExchangeError,  # Withdrawal amount exceeds the daily limit
                    '58202': ExchangeError,  # The minimum withdrawal amount for NEO is 1, and the amount must be an integer
                    '58203': InvalidAddress,  # Please add a withdrawal address
                    '58204': AccountSuspended,  # Withdrawal suspended
                    '58205': ExchangeError,  # Withdrawal amount exceeds the upper limit
                    '58206': ExchangeError,  # Withdrawal amount is lower than the lower limit
                    '58207': InvalidAddress,  # Withdrawal failed due to address error
                    '58208': ExchangeError,  # Withdrawal failed. Please link your email
                    '58209': ExchangeError,  # Withdrawal failed. Withdraw feature is not available for sub-accounts
                    '58210': ExchangeError,  # Withdrawal fee exceeds the upper limit
                    '58211': ExchangeError,  # Withdrawal fee is lower than the lower limit(withdrawal endpoint: incorrect fee)
                    '58212': ExchangeError,  # Withdrawal fee should be {0}% of the withdrawal amount
                    '58213': AuthenticationError,  # Please set trading password before withdrawal
                    '58300': ExchangeError,  # Deposit-address count exceeds the limit
                    '58350': InsufficientFunds,  # Insufficient balance
                    # Account error codes 59000-59999
                    '59000': ExchangeError,  # Your settings failed as you have positions or open orders
                    '59001': ExchangeError,  # Switching unavailable as you have borrowings
                    '59100': ExchangeError,  # You have open positions. Please cancel all open positions before changing the leverage
                    '59101': ExchangeError,  # You have pending orders with isolated positions. Please cancel all the pending orders and adjust the leverage
                    '59102': ExchangeError,  # Leverage exceeds the maximum leverage. Please adjust the leverage
                    '59103': InsufficientFunds,  # Leverage is too low and no sufficient margin in your account. Please adjust the leverage
                    '59104': ExchangeError,  # The leverage is too high. The borrowed position has exceeded the maximum position of self leverage. Please adjust the leverage
                    '59105': ExchangeError,  # Leverage can not be less than {0}. Please adjust the leverage
                    '59106': ExchangeError,  # The max available margin corresponding to your order tier is {0}. Please adjust your margin and place a new order
                    '59107': ExchangeError,  # You have pending orders under the service, please modify the leverage after canceling all pending orders
                    '59108': InsufficientFunds,  # Low leverage and insufficient margin, please adjust the leverage
                    '59109': ExchangeError,  # Account equity less than the required margin amount after adjustment. Please adjust the leverage
                    '59200': InsufficientFunds,  # Insufficient account balance
                    '59201': InsufficientFunds,  # Negative account balance
                    '59300': ExchangeError,  # Margin call failed. Position does not exist
                    '59301': ExchangeError,  # Margin adjustment failed for exceeding the max limit
                    '59401': ExchangeError,  # Holdings already reached the limit
                    '59500': ExchangeError,  # Only the APIKey of the main account has permission
                    '59501': ExchangeError,  # Only 50 APIKeys can be created per account
                    '59502': ExchangeError,  # Note name cannot be duplicate with the currently created APIKey note name
                    '59503': ExchangeError,  # Each APIKey can bind up to 20 IP addresses
                    '59504': ExchangeError,  # The sub account does not support the withdrawal function
                    '59505': ExchangeError,  # The passphrase format is incorrect
                    '59506': ExchangeError,  # APIKey does not exist
                    '59507': ExchangeError,  # The two accounts involved in a transfer must be two different sub accounts under the same parent account
                    '59508': AccountSuspended,  # The sub account of {0} is suspended
                    # WebSocket error Codes from 60000-63999
                    '60001': AuthenticationError,  # "OK_ACCESS_KEY" can not be empty
                    '60002': AuthenticationError,  # "OK_ACCESS_SIGN" can not be empty
                    '60003': AuthenticationError,  # "OK_ACCESS_PASSPHRASE" can not be empty
                    '60004': AuthenticationError,  # Invalid OK_ACCESS_TIMESTAMP
                    '60005': AuthenticationError,  # Invalid OK_ACCESS_KEY
                    '60006': InvalidNonce,  # Timestamp request expired
                    '60007': AuthenticationError,  # Invalid sign
                    '60008': AuthenticationError,  # Login is not supported for public channels
                    '60009': AuthenticationError,  # Login failed
                    '60010': AuthenticationError,  # Already logged in
                    '60011': AuthenticationError,  # Please log in
                    '60012': BadRequest,  # Illegal request
                    '60013': BadRequest,  # Invalid args
                    '60014': RateLimitExceeded,  # Requests too frequent
                    '60015': NetworkError,  # Connection closed as there was no data transmission in the last 30 seconds
                    '60016': ExchangeNotAvailable,  # Buffer is full, cannot write data
                    '60017': BadRequest,  # Invalid url path
                    '60018': BadRequest,  # The {0} {1} {2} {3} {4} does not exist
                    '60019': BadRequest,  # Invalid op {op}
                    '63999': ExchangeError,  # Internal system error
                },
                'broad': {
                },
            },
            'httpExceptions': {
                '429': ExchangeNotAvailable,  # https://github.com/ccxt/ccxt/issues/9612
            },
            'precisionMode': TICK_SIZE,
            'options': {
                'defaultNetwork': 'ERC20',
                'networks': {
                    'ETH': 'ERC20',
                    'TRX': 'TRC20',
                    'OMNI': 'Omni',
                },
                'layerTwo': {
                    'Lightning': True,
                    'Liquid': True,
                },
                'fetchOHLCV': {
                    # 'type': 'Candles',  # Candles or HistoryCandles, IndexCandles, MarkPriceCandles
                },
                'createOrder': 'privatePostTradeBatchOrders',  # or 'privatePostTradeOrder'
                'createMarketBuyOrderRequiresPrice': False,
                'fetchMarkets': ['spot', 'futures', 'swap', 'option'],  # spot, futures, swap, option
                'defaultType': 'spot',  # 'funding', 'spot', 'margin', 'futures', 'swap', 'option'
                # 'fetchBalance': {
                #     'type': 'spot',  # 'funding', 'trading', 'spot'
                # },
                'fetchLedger': {
                    'method': 'privateGetAccountBills',  # privateGetAccountBillsArchive, privateGetAssetBills
                },
                # 1 = SPOT, 3 = FUTURES, 5 = MARGIN, 6 = FUNDING, 9 = SWAP, 12 = OPTION, 18 = Unified account
                'accountsByType': {
                    'spot': '1',
                    'futures': '3',
                    'margin': '5',
                    'funding': '6',
                    'swap': '9',
                    'option': '12',
                    'trading': '18',  # unified trading account
                    'unified': '18',
                },
                'typesByAccount': {
                    '1': 'spot',
                    '3': 'futures',
                    '5': 'margin',
                    '6': 'funding',
                    '9': 'swap',
                    '12': 'option',
                    '18': 'trading',  # unified trading account
                },
                'brokerId': 'e847386590ce4dBC',
            },
            'commonCurrencies': {
                # OKEX refers to ERC20 version of Aeternity(AEToken)
                'AE': 'AET',  # https://github.com/ccxt/ccxt/issues/4981
                'BOX': 'DefiBox',
                'HOT': 'Hydro Protocol',
                'HSR': 'HC',
                'MAG': 'Maggie',
                'SBTC': 'Super Bitcoin',
                'TRADE': 'Unitrade',
                'YOYO': 'YOYOW',
                'WIN': 'WinToken',  # https://github.com/ccxt/ccxt/issues/5701
            },
        })

    async def fetch_status(self, params={}):
        response = await self.publicGetSystemStatus(params)
        #
        #     {
        #         "code":"0",
        #         "data":[
        #             {
        #                 "begin":"1621328400000",
        #                 "end":"1621329000000",
        #                 "href":"https://www.okex.com/support/hc/en-us/articles/360060882172",
        #                 "scheDesc":"",
        #                 "serviceType":"1",  # 0 WebSocket, 1 Spot/Margin, 2 Futures, 3 Perpetual, 4 Options, 5 Trading service
        #                 "state":"scheduled",  # ongoing, completed, canceled
        #                 "system":"classic",  # classic, unified
        #                 "title":"Classic Spot System Upgrade"
        #             },
        #         ],
        #         "msg":""
        #     }
        #
        data = self.safe_value(response, 'data', [])
        timestamp = self.milliseconds()
        update = {
            'info': response,
            'updated': timestamp,
            'status': 'ok',
            'eta': None,
        }
        for i in range(0, len(data)):
            event = data[i]
            state = self.safe_string(event, 'state')
            if state == 'ongoing':
                update['eta'] = self.safe_integer(event, 'end')
                update['status'] = 'maintenance'
        self.status = self.extend(self.status, update)
        return self.status

    async def fetch_time(self, params={}):
        response = await self.publicGetPublicTime(params)
        #
        #     {
        #         "code":"0",
        #         "data":[
        #             {"ts":"1621247923668"}
        #         ],
        #         "msg":""
        #     }
        #
        data = self.safe_value(response, 'data', [])
        first = self.safe_value(data, 0, {})
        return self.safe_integer(first, 'ts')

    async def fetch_markets(self, params={}):
        types = self.safe_value(self.options, 'fetchMarkets')
        result = []
        for i in range(0, len(types)):
            markets = await self.fetch_markets_by_type(types[i], params)
            result = self.array_concat(result, markets)
        return result

    def parse_markets(self, markets):
        result = []
        for i in range(0, len(markets)):
            result.append(self.parse_market(markets[i]))
        return result

    def parse_market(self, market):
        #
        #     {
        #         "alias":"",  # self_week, next_week, quarter, next_quarter
        #         "baseCcy":"BTC",
        #         "category":"1",
        #         "ctMult":"",
        #         "ctType":"",  # inverse, linear
        #         "ctVal":"",
        #         "ctValCcy":"",
        #         "expTime":"",
        #         "instId":"BTC-USDT",  # BTC-USD-210521, CSPR-USDT-SWAP, BTC-USD-210517-44000-C
        #         "instType":"SPOT",  # SPOT, FUTURES, SWAP, OPTION
        #         "lever":"10",
        #         "listTime":"1548133413000",
        #         "lotSz":"0.00000001",
        #         "minSz":"0.00001",
        #         "optType":"",
        #         "quoteCcy":"USDT",
        #         "settleCcy":"",
        #         "state":"live",
        #         "stk":"",
        #         "tickSz":"0.1",
        #         "uly":""
        #     }
        #
        #     {
        #         alias: "",
        #         baseCcy: "",
        #         category: "1",
        #         ctMult: "0.1",
        #         ctType: "",
        #         ctVal: "1",
        #         ctValCcy: "BTC",
        #         expTime: "1648195200000",
        #         instId: "BTC-USD-220325-194000-P",
        #         instType: "OPTION",
        #         lever: "",
        #         listTime: "1631262612280",
        #         lotSz: "1",
        #         minSz: "1",
        #         optType: "P",
        #         quoteCcy: "",
        #         settleCcy: "BTC",
        #         state: "live",
        #         stk: "194000",
        #         tickSz: "0.0005",
        #         uly: "BTC-USD"
        #     }
        #
        id = self.safe_string(market, 'instId')
        type = self.safe_string_lower(market, 'instType')
        spot = (type == 'spot')
        futures = (type == 'futures')
        swap = (type == 'swap')
        option = (type == 'option')
        contract = swap or futures or option
        baseId = self.safe_string(market, 'baseCcy')
        quoteId = self.safe_string(market, 'quoteCcy')
        settleCurrency = self.safe_string(market, 'settleCcy')
        settle = self.safe_currency_code(settleCurrency)
        underlying = self.safe_string(market, 'uly')
        if (underlying is not None) and not spot:
            parts = underlying.split('-')
            baseId = self.safe_string(parts, 0)
            quoteId = self.safe_string(parts, 1)
        inverse = baseId == settleCurrency
        linear = quoteId == settleCurrency
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        symbol = base + '/' + quote
        expiry = None
        if contract:
            symbol = symbol + ':' + settle
            expiry = self.safe_integer(market, 'expTime')
            if expiry is not None:
                ymd = self.yymmdd(expiry)
                symbol = symbol + '-' + ymd
            if option:
                strikePrice = self.safe_string(market, 'stk')
                optionType = self.safe_string(market, 'optType')
                symbol = symbol + '-' + strikePrice + '-' + optionType
        tickSize = self.safe_string(market, 'tickSz')
        precision = {
            'amount': self.safe_number(market, 'lotSz'),
            'price': self.parse_number(tickSize),
        }
        minAmountString = self.safe_string(market, 'minSz')
        minAmount = self.parse_number(minAmountString)
        minCost = None
        if (minAmount is not None) and (tickSize is not None):
            minCost = self.parse_number(Precise.string_mul(tickSize, minAmountString))
        active = True
        fees = self.safe_value_2(self.fees, type, 'trading', {})
        contractSize = None
        if contract:
            contractSize = self.safe_number(market, 'ctVal')
        leverage = self.safe_number(market, 'lever', 1)
        return self.extend(fees, {
            'id': id,
            'symbol': symbol,
            'base': base,
            'quote': quote,
            'baseId': baseId,
            'quoteId': quoteId,
            'settleId': settleCurrency,
            'settle': settle,
            'info': market,
            'type': type,
            'spot': spot,
            'futures': futures,
            'swap': swap,
            'contract': contract,
            'option': option,
            'linear': linear,
            'inverse': inverse,
            'active': active,
            'contractSize': contractSize,
            'precision': precision,
            'expiry': expiry,
            'expiryDatetime': self.iso8601(expiry),
            'limits': {
                'amount': {
                    'min': minAmount,
                    'max': None,
                },
                'price': {
                    'min': precision['price'],
                    'max': None,
                },
                'cost': {
                    'min': minCost,
                    'max': None,
                },
                'leverage': {
                    'max': leverage,
                },
            },
        })

    async def fetch_markets_by_type(self, type, params={}):
        uppercaseType = type.upper()
        request = {
            'instType': uppercaseType,
        }
        if uppercaseType == 'OPTION':
            defaultUnderlying = self.safe_value(self.options, 'defaultUnderlying', 'BTC-USD')
            currencyId = self.safe_string_2(params, 'uly', 'marketId', defaultUnderlying)
            if currencyId is None:
                raise ArgumentsRequired(self.id + ' fetchMarketsByType requires an underlying uly or marketId parameter for options markets')
            else:
                request['uly'] = currencyId
        response = await self.publicGetPublicInstruments(self.extend(request, params))
        #
        # spot, futures, swaps, options
        #
        #     {
        #         "code":"0",
        #         "data":[
        #             {
        #                 "alias":"",  # self_week, next_week, quarter, next_quarter
        #                 "baseCcy":"BTC",
        #                 "category":"1",
        #                 "ctMult":"",
        #                 "ctType":"",  # inverse, linear
        #                 "ctVal":"",
        #                 "ctValCcy":"",
        #                 "expTime":"",
        #                 "instId":"BTC-USDT",  # BTC-USD-210521, CSPR-USDT-SWAP, BTC-USD-210517-44000-C
        #                 "instType":"SPOT",  # SPOT, FUTURES, SWAP, OPTION
        #                 "lever":"10",
        #                 "listTime":"1548133413000",
        #                 "lotSz":"0.00000001",
        #                 "minSz":"0.00001",
        #                 "optType":"",
        #                 "quoteCcy":"USDT",
        #                 "settleCcy":"",
        #                 "state":"live",
        #                 "stk":"",
        #                 "tickSz":"0.1",
        #                 "uly":""
        #             }
        #         ],
        #         "msg":""
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_markets(data)

    def safe_network(self, networkId):
        networksById = {
            'Bitcoin': 'BTC',
            'Omni': 'OMNI',
            'TRON': 'TRC20',
        }
        return self.safe_string(networksById, networkId, networkId)

    async def fetch_currencies(self, params={}):
        # self endpoint requires authentication
        # while fetchCurrencies is a public API method by design
        # therefore we check the keys here
        # and fallback to generating the currencies from the markets
        if not self.check_required_credentials(False):
            return None
        # has['fetchCurrencies'] is currently set to False
        # it will reply with {"msg":"Request header “OK_ACCESS_KEY“ can't be empty.","code":"50103"}
        # if you attempt to access it without authentication
        response = await self.privateGetAssetCurrencies(params)
        #
        #     {
        #         "code":"0",
        #         "data":[
        #             {
        #                 "canDep":true,
        #                 "canInternal":true,
        #                 "canWd":true,
        #                 "ccy":"USDT",
        #                 "chain":"USDT-ERC20",
        #                 "maxFee":"40",
        #                 "minFee":"20",
        #                 "minWd":"2",
        #                 "name":""
        #             }
        #         ],
        #         "msg":""
        #     }
        #
        data = self.safe_value(response, 'data', [])
        result = {}
        dataByCurrencyId = self.group_by(data, 'ccy')
        currencyIds = list(dataByCurrencyId.keys())
        precision = self.parse_number('0.00000001')  # default precision, todo: fix "magic constants"
        for i in range(0, len(currencyIds)):
            currencyId = currencyIds[i]
            currency = self.safe_currency(currencyId)
            code = currency['code']
            chains = dataByCurrencyId[currencyId]
            networks = {}
            currencyActive = False
            for j in range(0, len(chains)):
                chain = chains[j]
                canDeposit = self.safe_value(chain, 'canDep')
                canWithdraw = self.safe_value(chain, 'canWd')
                canInternal = self.safe_value(chain, 'canInternal')
                active = True if (canDeposit and canWithdraw and canInternal) else False
                currencyActive = active if (currencyActive is None) else currencyActive
                networkId = self.safe_string(chain, 'chain')
                if networkId.find('-') >= 0:
                    parts = networkId.split('-')
                    chainPart = self.safe_string(parts, 1, networkId)
                    network = self.safe_network(chainPart)
                    mainNet = self.safe_value(chain, 'mainNet', False)
                    layerTwo = self.safe_value(self.options, 'layerTwo', {
                        'Liquid': True,
                        'Lightning': True,
                    })
                    if mainNet and not (chainPart in layerTwo):
                        # BTC lighting and liquid are both mainnet but not the same as BTC-Bitcoin
                        network = code
                    networks[network] = {
                        'info': chain,
                        'id': networkId,
                        'network': network,
                        'active': active,
                        'fee': self.safe_number(chain, 'minFee'),
                        'precision': None,
                        'limits': {
                            'withdraw': {
                                'min': self.safe_number(chain, 'minWd'),
                                'max': None,
                            },
                        },
                    }
            result[code] = {
                'info': None,
                'code': code,
                'id': currencyId,
                'name': None,
                'active': currencyActive,
                'fee': None,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': None,
                        'max': None,
                    },
                },
                'networks': networks,
            }
        return result

    async def fetch_order_book(self, symbol, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'instId': market['id'],
        }
        limit = 20 if (limit is None) else limit
        if limit is not None:
            request['sz'] = limit  # max 400
        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_value(response, 'data', [])
        first = self.safe_value(data, 0, {})
        timestamp = self.safe_integer(first, 'ts')
        return self.parse_order_book(first, symbol, timestamp)

    def parse_ticker(self, ticker, market=None):
        #
        #     {
        #         "instType":"SPOT",
        #         "instId":"ETH-BTC",
        #         "last":"0.07319",
        #         "lastSz":"0.044378",
        #         "askPx":"0.07322",
        #         "askSz":"4.2",
        #         "bidPx":"0.0732",
        #         "bidSz":"6.050058",
        #         "open24h":"0.07801",
        #         "high24h":"0.07975",
        #         "low24h":"0.06019",
        #         "volCcy24h":"11788.887619",
        #         "vol24h":"167493.829229",
        #         "ts":"1621440583784",
        #         "sodUtc0":"0.07872",
        #         "sodUtc8":"0.07345"
        #     }
        #
        timestamp = self.safe_integer(ticker, 'ts')
        marketId = self.safe_string(ticker, 'instId')
        market = self.safe_market(marketId, market, '-')
        symbol = market['symbol']
        last = self.safe_number(ticker, 'last')
        open = self.safe_number(ticker, 'open24h')
        quoteVolume = self.safe_number(ticker, 'volCcy24h')
        baseVolume = self.safe_number(ticker, 'vol24h')
        vwap = self.vwap(baseVolume, quoteVolume)
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_number(ticker, 'high24h'),
            'low': self.safe_number(ticker, 'low24h'),
            'bid': self.safe_number(ticker, 'bidPx'),
            'bidVolume': self.safe_number(ticker, 'bidSz'),
            'ask': self.safe_number(ticker, 'askPx'),
            'askVolume': self.safe_number(ticker, 'askSz'),
            'vwap': vwap,
            'open': open,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }

    async def fetch_ticker(self, symbol, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'instId': market['id'],
        }
        response = await self.publicGetMarketTicker(self.extend(request, params))
        #
        #     {
        #         "code":"0",
        #         "msg":"",
        #         "data":[
        #             {
        #                 "instType":"SPOT",
        #                 "instId":"ETH-BTC",
        #                 "last":"0.07319",
        #                 "lastSz":"0.044378",
        #                 "askPx":"0.07322",
        #                 "askSz":"4.2",
        #                 "bidPx":"0.0732",
        #                 "bidSz":"6.050058",
        #                 "open24h":"0.07801",
        #                 "high24h":"0.07975",
        #                 "low24h":"0.06019",
        #                 "volCcy24h":"11788.887619",
        #                 "vol24h":"167493.829229",
        #                 "ts":"1621440583784",
        #                 "sodUtc0":"0.07872",
        #                 "sodUtc8":"0.07345"
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        first = self.safe_value(data, 0, {})
        return self.parse_ticker(first, market)

    async def fetch_tickers_by_type(self, type, symbols=None, params={}):
        await self.load_markets()
        uppercaseType = type.upper()
        request = {
            'instType': type.upper(),
        }
        if uppercaseType == 'OPTION':
            defaultUnderlying = self.safe_value(self.options, 'defaultUnderlying', 'BTC-USD')
            currencyId = self.safe_string_2(params, 'uly', 'marketId', defaultUnderlying)
            if currencyId is None:
                raise ArgumentsRequired(self.id + ' fetchTickersByType requires an underlying uly or marketId parameter for options markets')
            else:
                request['uly'] = currencyId
        response = await self.publicGetMarketTickers(self.extend(request, params))
        #
        #     {
        #         "code":"0",
        #         "msg":"",
        #         "data":[
        #             {
        #                 "instType":"SPOT",
        #                 "instId":"BCD-BTC",
        #                 "last":"0.0000769",
        #                 "lastSz":"5.4788",
        #                 "askPx":"0.0000777",
        #                 "askSz":"3.2197",
        #                 "bidPx":"0.0000757",
        #                 "bidSz":"4.7509",
        #                 "open24h":"0.0000885",
        #                 "high24h":"0.0000917",
        #                 "low24h":"0.0000596",
        #                 "volCcy24h":"9.2877",
        #                 "vol24h":"124824.1985",
        #                 "ts":"1621441741434",
        #                 "sodUtc0":"0.0000905",
        #                 "sodUtc8":"0.0000729"
        #             },
        #         ]
        #     }
        #
        tickers = self.safe_value(response, 'data', [])
        return self.parse_tickers(tickers, symbols)

    async def fetch_tickers(self, symbols=None, params={}):
        defaultType = self.safe_string_2(self.options, 'fetchTickers', 'defaultType')
        type = self.safe_string(params, 'type', defaultType)
        return await self.fetch_tickers_by_type(type, symbols, self.omit(params, 'type'))

    def parse_trade(self, trade, market=None):
        #
        # public fetchTrades
        #
        #     {
        #         "instId":"ETH-BTC",
        #         "side":"sell",
        #         "sz":"0.119501",
        #         "px":"0.07065",
        #         "tradeId":"15826757",
        #         "ts":"1621446178316"
        #     }
        #
        # private fetchMyTrades
        #
        #     {
        #         "side":"buy",
        #         "fillSz":"0.007533",
        #         "fillPx":"2654.98",
        #         "fee":"-0.000007533",
        #         "ordId":"317321390244397056",
        #         "instType":"SPOT",
        #         "instId":"ETH-USDT",
        #         "clOrdId":"",
        #         "posSide":"net",
        #         "billId":"317321390265368576",
        #         "tag":"0",
        #         "execType":"T",
        #         "tradeId":"107601752",
        #         "feeCcy":"ETH",
        #         "ts":"1621927314985"
        #     }
        #
        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, 'fillPx', 'px')
        amount = self.safe_string_2(trade, 'fillSz', 'sz')
        side = self.safe_string(trade, 'side')
        orderId = self.safe_string(trade, 'ordId')
        feeCostString = self.safe_string(trade, 'fee')
        fee = None
        if feeCostString is not None:
            feeCostSigned = Precise.string_neg(feeCostString)
            feeCurrencyId = self.safe_string(trade, 'feeCcy')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCostSigned,
                'currency': feeCurrencyCode,
            }
        takerOrMaker = self.safe_string(trade, 'execType')
        if takerOrMaker == 'T':
            takerOrMaker = 'taker'
        elif takerOrMaker == 'M':
            takerOrMaker = 'maker'
        return self.safe_trade({
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': id,
            'order': orderId,
            'type': None,
            'takerOrMaker': takerOrMaker,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': None,
            'fee': fee,
        }, market)

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'instId': market['id'],
        }
        if limit is not None:
            request['limit'] = limit  # default 100
        response = await self.publicGetMarketTrades(self.extend(request, params))
        #
        #     {
        #         "code":"0",
        #         "msg":"",
        #         "data":[
        #             {"instId":"ETH-BTC","side":"sell","sz":"0.119501","px":"0.07065","tradeId":"15826757","ts":"1621446178316"},
        #             {"instId":"ETH-BTC","side":"sell","sz":"0.03","px":"0.07068","tradeId":"15826756","ts":"1621446178066"},
        #             {"instId":"ETH-BTC","side":"buy","sz":"0.507","px":"0.07069","tradeId":"15826755","ts":"1621446175085"},
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_trades(data, market, since, limit)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     [
        #         "1621447080000",  # timestamp
        #         "0.07073",  # open
        #         "0.07073",  # high
        #         "0.07064",  # low
        #         "0.07064",  # close
        #         "12.08863",  # base volume
        #         "0.854309"  # quote volume
        #     ]
        #
        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, 5),
        ]

    async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        price = self.safe_string(params, 'price')
        params = self.omit(params, 'price')
        if limit is None:
            limit = 100  # default 100, max 100
        request = {
            'instId': market['id'],
            'bar': self.timeframes[timeframe],
            'limit': limit,
        }
        defaultType = 'Candles'
        if since is not None:
            duration = self.parse_timeframe(timeframe)
            now = self.milliseconds()
            difference = now - since
            # if the since timestamp is more than limit candles back in the past
            if difference > limit * duration * 1000:
                defaultType = 'HistoryCandles'
            durationInMilliseconds = duration * 1000
            startTime = max(since - 1, 0)
            request['before'] = startTime
            request['after'] = self.sum(startTime, durationInMilliseconds * limit)
        options = self.safe_value(self.options, 'fetchOHLCV', {})
        defaultType = self.safe_string(options, 'type', defaultType)  # Candles or HistoryCandles
        type = self.safe_string(params, 'type', defaultType)
        params = self.omit(params, 'type')
        method = 'publicGetMarket' + type
        if price == 'mark':
            method = 'publicGetMarketMarkPriceCandles'
        elif price == 'index':
            method = 'publicGetMarketIndexCandles'
        response = await getattr(self, method)(self.extend(request, params))
        #
        #     {
        #         "code":"0",
        #         "msg":"",
        #         "data":[
        #             ["1621447080000","0.07073","0.07073","0.07064","0.07064","12.08863","0.854309"],
        #             ["1621447020000","0.0708","0.0709","0.0707","0.07072","58.517435","4.143309"],
        #             ["1621446960000","0.0707","0.07082","0.0707","0.07076","53.850841","3.810921"],
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_ohlcvs(data, market, timeframe, since, limit)

    async def fetch_funding_rate_history(self, symbol=None, since=None, limit=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        request = {
            '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.publicGetPublicFundingRateHistory(self.extend(request, params))
        #
        #     {
        #         "code":"0",
        #         "msg":"",
        #         "data":[
        #             {
        #                 "instType":"SWAP",
        #                 "instId":"BTC-USDT-SWAP",
        #                 "fundingRate":"0.018",
        #                 "realizedRate":"0.017",
        #                 "fundingTime":"1597026383085"
        #             },
        #             {
        #                 "instType":"SWAP",
        #                 "instId":"BTC-USDT-SWAP",
        #                 "fundingRate":"0.018",
        #                 "realizedRate":"0.017",
        #                 "fundingTime":"1597026383085"
        #             }
        #         ]
        #     }
        #
        rates = []
        data = self.safe_value(response, 'data')
        for i in range(0, len(data)):
            rate = data[i]
            timestamp = self.safe_number(rate, 'fundingTime')
            rates.append({
                'symbol': self.safe_symbol(self.safe_string(rate, 'instId')),
                'fundingRate': self.safe_number(rate, 'realizedRate'),
                'timestamp': timestamp,
                'datetime': self.iso8601(timestamp),
            })
        sorted = self.sort_by(rates, 'timestamp')
        return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)

    async def fetch_index_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        request = {
            'price': 'index',
        }
        return await self.fetch_ohlcv(symbol, timeframe, since, limit, self.extend(request, params))

    async def fetch_mark_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        request = {
            'price': 'mark',
        }
        return await self.fetch_ohlcv(symbol, timeframe, since, limit, self.extend(request, params))

    def parse_balance_by_type(self, type, response):
        if type == 'funding':
            return self.parse_funding_balance(response)
        else:
            return self.parse_trading_balance(response)

    def parse_trading_balance(self, response):
        result = {'info': response}
        data = self.safe_value(response, 'data', [])
        first = self.safe_value(data, 0, {})
        timestamp = self.safe_integer(first, 'uTime')
        details = self.safe_value(first, 'details', [])
        for i in range(0, len(details)):
            balance = details[i]
            currencyId = self.safe_string(balance, 'ccy')
            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, 'eq')
            availEq = self.safe_string(balance, 'availEq')
            if (len(eq) < 1) or (len(availEq) < 1):
                account['free'] = self.safe_string(balance, 'availBal')
                account['used'] = self.safe_string(balance, 'frozenBal')
            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):
        result = {'info': response}
        data = self.safe_value(response, 'data', [])
        for i in range(0, len(data)):
            balance = data[i]
            currencyId = self.safe_string(balance, 'ccy')
            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, 'bal')
            account['free'] = self.safe_string(balance, 'availBal')
            account['used'] = self.safe_string(balance, 'frozenBal')
            result[code] = account
        return self.safe_balance(result)

    def parse_trading_fee(self, fee, market=None):
        #
        #     {
        #         "category":"1",
        #         "delivery":"",
        #         "exercise":"",
        #         "instType":"SPOT",
        #         "level":"Lv1",
        #         "maker":"-0.0008",
        #         "taker":"-0.001",
        #         "ts":"1639043138472"
        #     }
        #
        return {
            'info': fee,
            'symbol': self.safe_symbol(None, market),
            'maker': self.safe_number(fee, 'maker'),
            'taker': self.safe_number(fee, 'taker'),
        }

    async def fetch_trading_fee(self, symbol, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'instType': market['type'].upper(),  # SPOT, MARGIN, SWAP, FUTURES, OPTION
            # 'instId': market['id'],  # only applicable to SPOT/MARGIN
            # 'uly': market['id'],  # only applicable to FUTURES/SWAP/OPTION
            # 'category': '1',  # 1 = Class A, 2 = Class B, 3 = Class C, 4 = Class D
        }
        if market['spot']:
            request['instId'] = market['id']
        elif market['swap'] or market['futures'] or market['option']:
            request['uly'] = market['baseId'] + '-' + market['quoteId']
        else:
            raise NotSupported(self.id + ' fetchTradingFee supports spot, swap, futures or option markets only')
        response = await self.privateGetAccountTradeFee(self.extend(request, params))
        #
        #     {
        #         "code":"0",
        #         "data":[
        #             {
        #                 "category":"1",
        #                 "delivery":"",
        #                 "exercise":"",
        #                 "instType":"SPOT",
        #                 "level":"Lv1",
        #                 "maker":"-0.0008",
        #                 "taker":"-0.001",
        #                 "ts":"1639043138472"
        #             }
        #         ],
        #         "msg":""
        #     }
        #
        data = self.safe_value(response, 'data', [])
        first = self.safe_value(data, 0, {})
        return self.parse_trading_fee(first, market)

    async def fetch_balance(self, params={}):
        await self.load_markets()
        defaultType = self.safe_string(self.options, 'defaultType')
        options = self.safe_value(self.options, 'fetchBalance', {})
        type = self.safe_string(options, 'type', defaultType)
        type = self.safe_string(params, 'type', type)
        params = self.omit(params, 'type')
        method = None
        if type == 'funding':
            method = 'privateGetAssetBalances'
        else:
            method = 'privateGetAccountBalance'
        request = {
            # 'ccy': 'BTC,ETH',  # comma-separated list of currency ids
        }
        response = await getattr(self, method)(self.extend(request, params))
        #
        #     {
        #         "code":"0",
        #         "data":[
        #             {
        #                 "adjEq":"",
        #                 "details":[
        #                     {
        #                         "availBal":"",
        #                         "availEq":"28.21006347",
        #                         "cashBal":"28.21006347",
        #                         "ccy":"USDT",
        #                         "crossLiab":"",
        #                         "disEq":"28.2687404020176",
        #                         "eq":"28.21006347",
        #                         "eqUsd":"28.2687404020176",
        #                         "frozenBal":"0",
        #                         "interest":"",
        #                         "isoEq":"0",
        #                         "isoLiab":"",
        #                         "liab":"",
        #                         "maxLoan":"",
        #                         "mgnRatio":"",
        #                         "notionalLever":"0",
        #                         "ordFrozen":"0",
        #                         "twap":"0",
        #                         "uTime":"1621556539861",
        #                         "upl":"0",
        #                         "uplLiab":""
        #                     }
        #                 ],
        #                 "imr":"",
        #                 "isoEq":"0",
        #                 "mgnRatio":"",
        #                 "mmr":"",
        #                 "notionalUsd":"",
        #                 "ordFroz":"",
        #                 "totalEq":"28.2687404020176",
        #                 "uTime":"1621556553510"
        #             }
        #         ],
        #         "msg":""
        #     }
        #
        #     {
        #         "code":"0",
        #         "data":[
        #             {
        #                 "adjEq":"",
        #                 "details":[
        #                     {
        #                         "availBal":"0.049",
        #                         "availEq":"",
        #                         "cashBal":"0.049",
        #                         "ccy":"BTC",
        #                         "crossLiab":"",
        #                         "disEq":"1918.55678",
        #                         "eq":"0.049",
        #                         "eqUsd":"1918.55678",
        #                         "frozenBal":"0",
        #                         "interest":"",
        #                         "isoEq":"",
        #                         "isoLiab":"",
        #                         "liab":"",
        #                         "maxLoan":"",
        #                         "mgnRatio":"",
        #                         "notionalLever":"",
        #                         "ordFrozen":"0",
        #                         "twap":"0",
        #                         "uTime":"1621973128591",
        #                         "upl":"",
        #                         "uplLiab":""
        #                     }
        #                 ],
        #                 "imr":"",
        #                 "isoEq":"",
        #                 "mgnRatio":"",
        #                 "mmr":"",
        #                 "notionalUsd":"",
        #                 "ordFroz":"",
        #                 "totalEq":"1918.55678",
        #                 "uTime":"1622045126908"
        #             }
        #         ],
        #         "msg":""
        #     }
        #
        # funding
        #
        #     {
        #         "code":"0",
        #         "data":[
        #             {
        #                 "availBal":"0.00005426",
        #                 "bal":0.0000542600000000,
        #                 "ccy":"BTC",
        #                 "frozenBal":"0"
        #             }
        #         ],
        #         "msg":""
        #     }
        #
        return self.parse_balance_by_type(type, response)

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'instId': market['id'],
            #
            #     Simple:
            #     - SPOT and OPTION buyer: cash
            #
            #     Single-currency margin:
            #     - Isolated MARGIN: isolated
            #     - Cross MARGIN: cross
            #     - Cross SPOT: cash
            #     - Cross FUTURES/SWAP/OPTION: cross
            #     - Isolated FUTURES/SWAP/OPTION: isolated
            #
            #     Multi-currency margin:
            #     - Isolated MARGIN: isolated
            #     - Cross SPOT: cross
            #     - Cross FUTURES/SWAP/OPTION: cross
            #     - Isolated FUTURES/SWAP/OPTION: isolated
            #
            # 'ccy': currency['id'],  # only applicable to cross MARGIN orders in single-currency margin
            # 'clOrdId': clientOrderId,  # up to 32 characters, must be unique
            # 'tag': tag,  # up to 8 characters
            #
            #     In long/short mode, side and posSide need to be combined
            #
            #     buy with long means open long
            #     sell with long means close long
            #     sell with short means open short
            #     buy with short means close short
            #
            'side': side,
            # 'posSide': 'long',  # long, short,  # required in the long/short mode, and can only be long or short
            'ordType': type,  # market, limit, post_only, fok, ioc
            #
            #     for SPOT/MARGIN bought and sold at a limit price, sz refers to the amount of trading currency
            #     for SPOT/MARGIN bought at a market price, sz refers to the amount of quoted currency
            #     for SPOT/MARGIN sold at a market price, sz refers to the amount of trading currency
            #     for FUTURES/SWAP/OPTION buying and selling, sz refers to the number of contracts
            #
            # 'sz': self.amount_to_precision(symbol, amount),
            # 'px': self.price_to_precision(symbol, price),  # limit orders only
            # 'reduceOnly': False,  # MARGIN orders only
        }
        tdMode = self.safe_string_lower(params, 'tdMode')
        if market['spot']:
            request['tdMode'] = 'cash'
        elif market['contract']:
            if tdMode is None:
                raise ArgumentsRequired(self.id + ' params["tdMode"] is required to be either "isolated" or "cross"')
            elif (tdMode != 'isolated') and (tdMode != 'cross'):
                raise BadRequest(self.id + ' params["tdMode"] must be either "isolated" or "cross"')
        postOnly = self.safe_value(params, 'postOnly', False)
        if postOnly:
            request['ordType'] = 'post_only'
            params = self.omit(params, ['postOnly'])
        clientOrderId = self.safe_string_2(params, 'clOrdId', 'clientOrderId')
        if clientOrderId is None:
            brokerId = self.safe_string(self.options, 'brokerId')
            if brokerId is not None:
                request['clOrdId'] = brokerId + self.uuid16()
        else:
            request['clOrdId'] = clientOrderId
            params = self.omit(params, ['clOrdId', 'clientOrderId'])
        request['sz'] = self.amount_to_precision(symbol, amount)
        if type == 'market':
            if market['type'] == 'spot' and side == 'buy':
                # spot market buy: "sz" can refer either to base currency units or to quote currency units
                # see documentation: https://www.okex.com/docs-v5/en/#rest-api-trade-place-order
                defaultTgtCcy = self.safe_string(self.options, 'tgtCcy', 'base_ccy')
                tgtCcy = self.safe_string(params, 'tgtCcy', defaultTgtCcy)
                if tgtCcy == 'quote_ccy':
                    # quote_ccy: sz refers to units of quote currency
                    request['tgtCcy'] = 'quote_ccy'
                    notional = self.safe_number(params, 'sz')
                    createMarketBuyOrderRequiresPrice = self.safe_value(self.options, 'createMarketBuyOrderRequiresPrice', True)
                    if createMarketBuyOrderRequiresPrice:
                        if price is not None:
                            if notional is None:
                                notional = amount * price
                        elif notional is None:
                            raise InvalidOrder(self.id + " createOrder() requires the price argument with market buy orders to calculate total order cost(amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = False and supply the total cost value in the 'amount' argument or in the 'sz' extra parameter(the exchange-specific behaviour)")
                    else:
                        notional = amount if (notional is None) else notional
                    precision = market['precision']['price']
                    request['sz'] = self.decimal_to_precision(notional, TRUNCATE, precision, self.precisionMode)
                else:
                    # base_ccy: sz refers to units of base currency
                    request['tgtCcy'] = 'base_ccy'
                params = self.omit(params, ['tgtCcy'])
        else:
            # non-market orders
            request['px'] = self.price_to_precision(symbol, price)
        extendedRequest = None
        defaultMethod = self.safe_string(self.options, 'createOrder', 'privatePostTradeBatchOrders')  # or privatePostTradeOrder
        if defaultMethod == 'privatePostTradeOrder':
            extendedRequest = self.extend(request, params)
        elif defaultMethod == 'privatePostTradeBatchOrders':
            # keep the request body the same
            # submit a single order in an array to the batch order endpoint
            # because it has a lower ratelimit
            extendedRequest = [self.extend(request, params)]
        else:
            raise ExchangeError(self.id + ' self.options["createOrder"] must be either privatePostTradeBatchOrders or privatePostTradeOrder')
        response = await getattr(self, defaultMethod)(extendedRequest)
        #
        #     {
        #         "code": "0",
        #         "msg": "",
        #         "data": [
        #             {
        #                 "clOrdId": "oktswap6",
        #                 "ordId": "312269865356374016",
        #                 "tag": "",
        #                 "sCode": "0",
        #                 "sMsg": ""
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        first = self.safe_value(data, 0)
        order = self.parse_order(first, market)
        return self.extend(order, {
            'type': type,
            'side': side,
        })

    async def cancel_order(self, id, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'instId': market['id'],
            # 'ordId': id,  # either ordId or clOrdId is required
            # 'clOrdId': clientOrderId,
        }
        clientOrderId = self.safe_string_2(params, 'clOrdId', 'clientOrderId')
        if clientOrderId is not None:
            request['clOrdId'] = clientOrderId
        else:
            request['ordId'] = id
        query = self.omit(params, ['clOrdId', 'clientOrderId'])
        response = await self.privatePostTradeCancelOrder(self.extend(request, query))
        # {"code":"0","data":[{"clOrdId":"","ordId":"317251910906576896","sCode":"0","sMsg":""}],"msg":""}
        data = self.safe_value(response, 'data', [])
        order = self.safe_value(data, 0)
        return self.parse_order(order, market)

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

    def parse_order(self, order, market=None):
        #
        # createOrder
        #
        #     {
        #         "clOrdId": "oktswap6",
        #         "ordId": "312269865356374016",
        #         "tag": "",
        #         "sCode": "0",
        #         "sMsg": ""
        #     }
        #
        # fetchOrder, fetchOpenOrders
        #
        #     {
        #         "accFillSz":"0",
        #         "avgPx":"",
        #         "cTime":"1621910749815",
        #         "category":"normal",
        #         "ccy":"",
        #         "clOrdId":"",
        #         "fee":"0",
        #         "feeCcy":"ETH",
        #         "fillPx":"",
        #         "fillSz":"0",
        #         "fillTime":"",
        #         "instId":"ETH-USDT",
        #         "instType":"SPOT",
        #         "lever":"",
        #         "ordId":"317251910906576896",
        #         "ordType":"limit",
        #         "pnl":"0",
        #         "posSide":"net",
        #         "px":"2000",
        #         "rebate":"0",
        #         "rebateCcy":"USDT",
        #         "side":"buy",
        #         "slOrdPx":"",
        #         "slTriggerPx":"",
        #         "state":"live",
        #         "sz":"0.001",
        #         "tag":"",
        #         "tdMode":"cash",
        #         "tpOrdPx":"",
        #         "tpTriggerPx":"",
        #         "tradeId":"",
        #         "uTime":"1621910749815"
        #     }
        #
        id = self.safe_string(order, 'ordId')
        timestamp = self.safe_integer(order, 'cTime')
        lastTradeTimestamp = self.safe_integer(order, 'fillTime')
        side = self.safe_string(order, 'side')
        type = self.safe_string(order, 'ordType')
        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')
        symbol = self.safe_symbol(marketId, market, '-')
        filled = self.safe_string(order, 'accFillSz')
        price = self.safe_string_2(order, 'px', 'slOrdPx')
        average = self.safe_string(order, 'avgPx')
        status = self.parse_order_status(self.safe_string(order, 'state'))
        feeCostString = self.safe_string(order, 'fee')
        amount = None
        cost = None
        # spot market buy: "sz" can refer either to base currency units or to quote currency units
        # see documentation: https://www.okex.com/docs-v5/en/#rest-api-trade-place-order
        defaultTgtCcy = self.safe_string(self.options, 'tgtCcy', 'base_ccy')
        tgtCcy = self.safe_string(order, 'tgtCcy', defaultTgtCcy)
        instType = self.safe_string(order, 'instType')
        if (side == 'buy') and (type == 'market') and (instType == 'SPOT') and (tgtCcy == 'quote_ccy'):
            # "sz" refers to the cost
            cost = self.safe_string(order, 'sz')
        else:
            # "sz" refers to the trade currency amount
            amount = self.safe_string(order, 'sz')
        fee = None
        if feeCostString is not None:
            feeCostSigned = Precise.string_neg(feeCostString)
            feeCurrencyId = self.safe_string(order, 'feeCcy')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': self.parse_number(feeCostSigned),
                'currency': feeCurrencyCode,
            }
        clientOrderId = self.safe_string(order, 'clOrdId')
        if (clientOrderId is not None) and (len(clientOrderId) < 1):
            clientOrderId = None  # fix empty clientOrderId string
        stopPrice = self.safe_number(order, 'slTriggerPx')
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': postOnly,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'average': average,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'remaining': None,
            'status': status,
            'fee': fee,
            'trades': None,
        }, market)

    async def fetch_order(self, id, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'instId': market['id'],
            # 'clOrdId': 'abcdef12345',  # optional, [a-z0-9]{1,32}
            # 'ordId': id,
        }
        clientOrderId = self.safe_string_2(params, 'clOrdId', 'clientOrderId')
        if clientOrderId is not None:
            request['clOrdId'] = clientOrderId
        else:
            request['ordId'] = id
        query = self.omit(params, ['clOrdId', 'clientOrderId'])
        response = await self.privateGetTradeOrder(self.extend(request, query))
        #
        #     {
        #         "code":"0",
        #         "data":[
        #             {
        #                 "accFillSz":"0",
        #                 "avgPx":"",
        #                 "cTime":"1621910749815",
        #                 "category":"normal",
        #                 "ccy":"",
        #                 "clOrdId":"",
        #                 "fee":"0",
        #                 "feeCcy":"ETH",
        #                 "fillPx":"",
        #                 "fillSz":"0",
        #                 "fillTime":"",
        #                 "instId":"ETH-USDT",
        #                 "instType":"SPOT",
        #                 "lever":"",
        #                 "ordId":"317251910906576896",
        #                 "ordType":"limit",
        #                 "pnl":"0",
        #                 "posSide":"net",
        #                 "px":"2000",
        #                 "rebate":"0",
        #                 "rebateCcy":"USDT",
        #                 "side":"buy",
        #                 "slOrdPx":"",
        #                 "slTriggerPx":"",
        #                 "state":"live",
        #                 "sz":"0.001",
        #                 "tag":"",
        #                 "tdMode":"cash",
        #                 "tpOrdPx":"",
        #                 "tpTriggerPx":"",
        #                 "tradeId":"",
        #                 "uTime":"1621910749815"
        #             }
        #         ],
        #         "msg":""
        #     }
        #
        data = self.safe_value(response, 'data', [])
        order = self.safe_value(data, 0)
        return self.parse_order(order, market)

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {
            # 'instType': 'SPOT',  # SPOT, MARGIN, SWAP, FUTURES, OPTION
            # 'uly': currency['id'],
            # 'instId': market['id'],
            # 'ordType': 'limit',  # market, limit, post_only, fok, ioc, comma-separated
            # 'state': 'live',  # live, partially_filled
            # 'after': orderId,
            # 'before': orderId,
            # 'limit': limit,  # default 100, max 100
        }
        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
        response = await self.privateGetTradeOrdersPending(self.extend(request, params))
        #
        #     {
        #         "code":"0",
        #         "data":[
        #             {
        #                 "accFillSz":"0",
        #                 "avgPx":"",
        #                 "cTime":"1621910749815",
        #                 "category":"normal",
        #                 "ccy":"",
        #                 "clOrdId":"",
        #                 "fee":"0",
        #                 "feeCcy":"ETH",
        #                 "fillPx":"",
        #                 "fillSz":"0",
        #                 "fillTime":"",
        #                 "instId":"ETH-USDT",
        #                 "instType":"SPOT",
        #                 "lever":"",
        #                 "ordId":"317251910906576896",
        #                 "ordType":"limit",
        #                 "pnl":"0",
        #                 "posSide":"net",
        #                 "px":"2000",
        #                 "rebate":"0",
        #                 "rebateCcy":"USDT",
        #                 "side":"buy",
        #                 "slOrdPx":"",
        #                 "slTriggerPx":"",
        #                 "state":"live",
        #                 "sz":"0.001",
        #                 "tag":"",
        #                 "tdMode":"cash",
        #                 "tpOrdPx":"",
        #                 "tpTriggerPx":"",
        #                 "tradeId":"",
        #                 "uTime":"1621910749815"
        #             }
        #         ],
        #         "msg":""
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_orders(data, market, since, limit)

    async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        defaultType = self.safe_string(self.options, 'defaultType')
        options = self.safe_value(self.options, 'fetchClosedOrders', {})
        type = self.safe_string(options, 'type', defaultType)
        type = self.safe_string(params, 'type', type)
        params = self.omit(params, 'type')
        request = {
            # 'instType': type.upper(),  # SPOT, MARGIN, SWAP, FUTURES, OPTION
            # 'uly': currency['id'],
            # 'instId': market['id'],
            # 'ordType': 'limit',  # market, limit, post_only, fok, ioc, comma-separated
            # 'state': 'filled',  # filled, canceled
            # 'after': orderId,
            # 'before': orderId,
            # 'limit': limit,  # default 100, max 100
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            if market['futures'] or market['swap']:
                type = market['type']
            request['instId'] = market['id']
        request['instType'] = type.upper()
        if limit is not None:
            request['limit'] = limit  # default 100, max 100
        method = self.safe_string(options, 'method', 'privateGetTradeOrdersHistory')
        response = await getattr(self, method)(self.extend(request, params))
        #
        #     {
        #         "code":"0",
        #         "data":[
        #             {
        #                 "accFillSz":"0",
        #                 "avgPx":"",
        #                 "cTime":"1621910749815",
        #                 "category":"normal",
        #                 "ccy":"",
        #                 "clOrdId":"",
        #                 "fee":"0",
        #                 "feeCcy":"ETH",
        #                 "fillPx":"",
        #                 "fillSz":"0",
        #                 "fillTime":"",
        #                 "instId":"ETH-USDT",
        #                 "instType":"SPOT",
        #                 "lever":"",
        #                 "ordId":"317251910906576896",
        #                 "ordType":"limit",
        #                 "pnl":"0",
        #                 "posSide":"net",
        #                 "px":"2000",
        #                 "rebate":"0",
        #                 "rebateCcy":"USDT",
        #                 "side":"buy",
        #                 "slOrdPx":"",
        #                 "slTriggerPx":"",
        #                 "state":"live",
        #                 "sz":"0.001",
        #                 "tag":"",
        #                 "tdMode":"cash",
        #                 "tpOrdPx":"",
        #                 "tpTriggerPx":"",
        #                 "tradeId":"",
        #                 "uTime":"1621910749815"
        #             }
        #         ],
        #         "msg":""
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_orders(data, market, since, limit)

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        defaultType = self.safe_string(self.options, 'defaultType')
        options = self.safe_value(self.options, 'fetchMyTrades', {})
        type = self.safe_string(options, 'type', defaultType)
        params = self.omit(params, 'type')
        await self.load_markets()
        request = {
            # 'instType': 'SPOT',  # SPOT, MARGIN, SWAP, FUTURES, OPTION
            # 'uly': currency['id'],
            # 'instId': market['id'],
            # 'ordId': orderId,
            # 'after': billId,
            # 'before': billId,
            # 'limit': limit,  # default 100, max 100
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['instId'] = market['id']
            type = market['type']
        request['instType'] = type.upper()
        if limit is not None:
            request['limit'] = limit  # default 100, max 100
        response = await self.privateGetTradeFillsHistory(self.extend(request, params))
        #
        #     {
        #         "code":"0",
        #         "data":[
        #             {
        #                 "side":"buy",
        #                 "fillSz":"0.007533",
        #                 "fillPx":"2654.98",
        #                 "fee":"-0.000007533",
        #                 "ordId":"317321390244397056",
        #                 "instType":"SPOT",
        #                 "instId":"ETH-USDT",
        #                 "clOrdId":"",
        #                 "posSide":"net",
        #                 "billId":"317321390265368576",
        #                 "tag":"0",
        #                 "execType":"T",
        #                 "tradeId":"107601752",
        #                 "feeCcy":"ETH",
        #                 "ts":"1621927314985"
        #             }
        #         ],
        #         "msg":""
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_trades(data, market, since, limit, params)

    async def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
        request = {
            # 'instrument_id': market['id'],
            'ordId': id,
            # 'after': '1',  # return the page after the specified page number
            # 'before': '1',  # return the page before the specified page number
            # 'limit': limit,  # optional, number of results per request, default = maximum = 100
        }
        return await self.fetch_my_trades(symbol, since, limit, self.extend(request, params))

    async def fetch_ledger(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        options = self.safe_value(self.options, 'fetchLedger', {})
        method = self.safe_string(options, 'method')
        method = self.safe_string(params, 'method', method)
        params = self.omit(params, 'method')
        request = {
            # 'instType': None,  # 'SPOT', 'MARGIN', 'SWAP', 'FUTURES", 'OPTION'
            # 'ccy': None,  # currency['id'],
            # 'mgnMode': None,  # 'isolated', 'cross'
            # 'ctType': None,  # 'linear', 'inverse', only applicable to FUTURES/SWAP
            # 'type': None,
            #     1 Transfer,
            #     2 Trade,
            #     3 Delivery,
            #     4 Auto token conversion,
            #     5 Liquidation,
            #     6 Margin transfer,
            #     7 Interest deduction,
            #     8 Funding rate,
            #     9 ADL,
            #     10 Clawback,
            #     11 System token conversion
            # 'subType': None,
            #     1 Buy
            #     2 Sell
            #     3 Open long
            #     4 Open short
            #     5 Close long
            #     6 Close short
            #     9 Interest deduction
            #     11 Transfer in
            #     12 Transfer out
            #     160 Manual margin increase
            #     161 Manual margin decrease
            #     162 Auto margin increase
            #     110 Auto buy
            #     111 Auto sell
            #     118 System token conversion transfer in
            #     119 System token conversion transfer out
            #     100 Partial liquidation close long
            #     101 Partial liquidation close short
            #     102 Partial liquidation buy
            #     103 Partial liquidation sell
            #     104 Liquidation long
            #     105 Liquidation short
            #     106 Liquidation buy
            #     107 Liquidation sell
            #     110 Liquidation transfer in
            #     111 Liquidation transfer out
            #     125 ADL close long
            #     126 ADL close short
            #     127 ADL buy
            #     128 ADL sell
            #     170 Exercised
            #     171 Counterparty exercised
            #     172 Expired OTM
            #     112 Delivery long
            #     113 Delivery short
            #     117 Delivery/Exercise clawback
            #     173 Funding fee expense
            #     174 Funding fee income
            #
            # 'after': 'id',  # return records earlier than the requested bill id
            # 'before': 'id',  # return records newer than the requested bill id
            # 'limit': 100,  # default 100, max 100
        }
        if limit is not None:
            request['limit'] = limit
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['ccy'] = currency['id']
        response = await getattr(self, method)(self.extend(request, params))
        #
        # privateGetAccountBills, privateGetAccountBillsArchive
        #
        #     {
        #         "code": "0",
        #         "msg": "",
        #         "data": [
        #             {
        #                 "bal": "0.0000819307998198",
        #                 "balChg": "-664.2679586599999802",
        #                 "billId": "310394313544966151",
        #                 "ccy": "USDT",
        #                 "fee": "0",
        #                 "from": "",
        #                 "instId": "LTC-USDT",
        #                 "instType": "SPOT",
        #                 "mgnMode": "cross",
        #                 "notes": "",
        #                 "ordId": "310394313519800320",
        #                 "pnl": "0",
        #                 "posBal": "0",
        #                 "posBalChg": "0",
        #                 "subType": "2",
        #                 "sz": "664.26795866",
        #                 "to": "",
        #                 "ts": "1620275771196",
        #                 "type": "2"
        #             }
        #         ]
        #     }
        #
        # privateGetAssetBills
        #
        #     {
        #         "code": "0",
        #         "msg": "",
        #         "data": [
        #             {
        #                 "billId": "12344",
        #                 "ccy": "BTC",
        #                 "balChg": "2",
        #                 "bal": "12",
        #                 "type": "1",
        #                 "ts": "1597026383085"
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_ledger(data, currency, since, limit)

    def parse_ledger_entry_type(self, type):
        types = {
            '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, currency=None):
        #
        # privateGetAccountBills, privateGetAccountBillsArchive
        #
        #     {
        #         "bal": "0.0000819307998198",
        #         "balChg": "-664.2679586599999802",
        #         "billId": "310394313544966151",
        #         "ccy": "USDT",
        #         "fee": "0",
        #         "from": "",
        #         "instId": "LTC-USDT",
        #         "instType": "SPOT",
        #         "mgnMode": "cross",
        #         "notes": "",
        #         "ordId": "310394313519800320",
        #         "pnl": "0",
        #         "posBal": "0",
        #         "posBalChg": "0",
        #         "subType": "2",
        #         "sz": "664.26795866",
        #         "to": "",
        #         "ts": "1620275771196",
        #         "type": "2"
        #     }
        #
        # privateGetAssetBills
        #
        #     {
        #         "billId": "12344",
        #         "ccy": "BTC",
        #         "balChg": "2",
        #         "bal": "12",
        #         "type": "1",
        #         "ts": "1597026383085"
        #     }
        #
        id = self.safe_string(item, 'billId')
        account = None
        referenceId = self.safe_string(item, 'ordId')
        referenceAccount = None
        type = self.parse_ledger_entry_type(self.safe_string(item, 'type'))
        code = self.safe_currency_code(self.safe_string(item, 'ccy'), currency)
        amountString = self.safe_string(item, 'balChg')
        amount = self.parse_number(amountString)
        timestamp = self.safe_integer(item, 'ts')
        feeCostString = self.safe_string(item, 'fee')
        fee = None
        if feeCostString is not None:
            fee = {
                'cost': self.parse_number(Precise.string_neg(feeCostString)),
                'currency': code,
            }
        before = None
        afterString = self.safe_string(item, 'bal')
        after = self.parse_number(afterString)
        status = 'ok'
        marketId = self.safe_string(item, 'instId')
        symbol = None
        if marketId in self.markets_by_id:
            market = self.markets_by_id[marketId]
            symbol = market['symbol']
        return {
            'id': id,
            'info': item,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'account': account,
            'referenceId': referenceId,
            'referenceAccount': referenceAccount,
            'type': type,
            'currency': code,
            'symbol': symbol,
            'amount': amount,
            'before': before,  # balance before
            'after': after,  # balance after
            'status': status,
            'fee': fee,
        }

    def parse_deposit_address(self, depositAddress, currency=None):
        #
        #     {
        #         "addr": "okbtothemoon",
        #         "memo": "971668",  # may be missing
        #         "tag":"52055",  # may be missing
        #         "pmtId": "",  # may be missing
        #         "ccy": "BTC",
        #         "to": "6",  # 1 SPOT, 3 FUTURES, 6 FUNDING, 9 SWAP, 12 OPTION, 18 Unified account
        #         "selected": True
        #     }
        #
        #     {
        #         "ccy":"usdt-erc20",
        #         "to":"6",
        #         "addr":"0x696abb81974a8793352cbd33aadcf78eda3cfdfa",
        #         "selected":true
        #     }
        #
        #     {
        #       "chain": "ETH-OKExChain",
        #       "ctAddr": "72315c",
        #       "ccy": "ETH",
        #       "to": "6",
        #       "addr": "0x1c9f2244d1ccaa060bd536827c18925db10db102",
        #       "selected": True
        #     }
        #
        address = self.safe_string(depositAddress, 'addr')
        tag = self.safe_string_2(depositAddress, 'tag', 'pmtId')
        tag = self.safe_string(depositAddress, 'memo', tag)
        currencyId = self.safe_string(depositAddress, 'ccy')
        currency = self.safe_currency(currencyId, currency)
        code = currency['code']
        chain = self.safe_string(depositAddress, 'chain')
        networks = self.safe_value(currency, 'networks', {})
        networksById = self.index_by(networks, 'id')
        networkData = self.safe_value(networksById, chain)
        network = self.safe_string(networkData, 'network')
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'network': network,
            'info': depositAddress,
        }

    async def fetch_deposit_addresses_by_network(self, code, params={}):
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'ccy': currency['id'],
        }
        response = await self.privateGetAssetDepositAddress(self.extend(request, params))
        #
        #     {
        #         "code": "0",
        #         "msg": "",
        #         "data": [
        #             {
        #                 "addr": "okbtothemoon",
        #                 "memo": "971668",  # may be missing
        #                 "tag":"52055",  # may be missing
        #                 "pmtId": "",  # may be missing
        #                 "ccy": "BTC",
        #                 "to": "6",  # 1 SPOT, 3 FUTURES, 6 FUNDING, 9 SWAP, 12 OPTION, 18 Unified account
        #                 "selected": True
        #             },
        #             # {"ccy":"usdt-erc20","to":"6","addr":"0x696abb81974a8793352cbd33aadcf78eda3cfdfa","selected":true},
        #             # {"ccy":"usdt-trc20","to":"6","addr":"TRrd5SiSZrfQVRKm4e9SRSbn2LNTYqCjqx","selected":true},
        #             # {"ccy":"usdt_okexchain","to":"6","addr":"0x696abb81974a8793352cbd33aadcf78eda3cfdfa","selected":true},
        #             # {"ccy":"usdt_kip20","to":"6","addr":"0x696abb81974a8793352cbd33aadcf78eda3cfdfa","selected":true},
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        filtered = self.filter_by(data, 'selected', True)
        parsed = self.parse_deposit_addresses(filtered, [code], False)
        return self.index_by(parsed, 'network')

    async def fetch_deposit_address(self, code, params={}):
        rawNetwork = self.safe_string_upper(params, 'network')
        networks = self.safe_value(self.options, 'networks', {})
        network = self.safe_string(networks, rawNetwork, rawNetwork)
        params = self.omit(params, 'network')
        response = await self.fetch_deposit_addresses_by_network(code, params)
        result = None
        if network is None:
            result = self.safe_value(response, code)
            if result is None:
                alias = self.safe_string(networks, code, code)
                result = self.safe_value(response, alias)
                if result is None:
                    defaultNetwork = self.safe_string(self.options, 'defaultNetwork', 'ERC20')
                    result = self.safe_value(response, defaultNetwork)
                    if result is None:
                        values = list(response.values())
                        result = self.safe_value(values, 0)
                        if result is None:
                            raise InvalidAddress(self.id + ' fetchDepositAddress() cannot find deposit address for ' + code)
            return result
        result = self.safe_value(response, network)
        if result is None:
            raise InvalidAddress(self.id + ' fetchDepositAddress() cannot find ' + network + ' deposit address for ' + code)
        return result

    async def withdraw(self, code, amount, address, tag=None, params={}):
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        self.check_address(address)
        await self.load_markets()
        currency = self.currency(code)
        if tag is not None:
            address = address + ':' + tag
        fee = self.safe_string(params, 'fee')
        if fee is None:
            raise ArgumentsRequired(self.id + " withdraw() requires a 'fee' string parameter, network transaction fee must be ≥ 0. Withdrawals to OKCoin or OKEx are fee-free, please set '0'. Withdrawing to external digital asset address requires network transaction fee.")
        request = {
            'ccy': currency['id'],
            'toAddr': address,
            'dest': '4',  # 2 = OKCoin International, 3 = OKEx 4 = others
            'amt': self.number_to_string(amount),
            'fee': self.number_to_string(fee),  # withdrawals to OKCoin or OKEx are fee-free, please set 0
        }
        if 'password' in params:
            request['pwd'] = params['password']
        elif 'pwd' in params:
            request['pwd'] = params['pwd']
        networks = self.safe_value(self.options, 'networks', {})
        network = self.safe_string_upper(params, 'network')  # self line allows the user to specify either ERC20 or ETH
        network = self.safe_string(networks, network, network)  # handle ETH>ERC20 alias
        if network is not None:
            request['chain'] = currency['id'] + '-' + network
            params = self.omit(params, 'network')
        query = self.omit(params, ['fee', 'password', 'pwd'])
        if not ('pwd' in request):
            raise ExchangeError(self.id + ' withdraw() requires a password parameter or a pwd parameter, it must be the funding password, not the API passphrase')
        response = await self.privatePostAssetWithdrawal(self.extend(request, query))
        #
        #     {
        #         "code": "0",
        #         "msg": "",
        #         "data": [
        #             {
        #                 "amt": "0.1",
        #                 "wdId": "67485",
        #                 "ccy": "BTC"
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        transaction = self.safe_value(data, 0)
        return self.parse_transaction(transaction, currency)

    async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {
            # 'ccy': currency['id'],
            # 'state': 2,  # 0 waiting for confirmation, 1 deposit credited, 2 deposit successful
            # 'after': since,
            # 'before' self.milliseconds(),
            # 'limit': limit,  # default 100, max 100
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['ccy'] = 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
        response = await self.privateGetAssetDepositHistory(self.extend(request, params))
        #
        #     {
        #         "code": "0",
        #         "msg": "",
        #         "data": [
        #             {
        #                 "amt": "0.01044408",
        #                 "txId": "1915737_3_0_0_asset",
        #                 "ccy": "BTC",
        #                 "from": "13801825426",
        #                 "to": "",
        #                 "ts": "1597026383085",
        #                 "state": "2",
        #                 "depId": "4703879"
        #             },
        #             {
        #                 "amt": "491.6784211",
        #                 "txId": "1744594_3_184_0_asset",
        #                 "ccy": "OKB",
        #                 "from": "",
        #                 "to": "",
        #                 "ts": "1597026383085",
        #                 "state": "2",
        #                 "depId": "4703809"
        #             },
        #             {
        #                 "amt": "223.18782496",
        #                 "txId": "6d892c669225b1092c780bf0da0c6f912fc7dc8f6b8cc53b003288624c",
        #                 "ccy": "USDT",
        #                 "from": "",
        #                 "to": "39kK4XvgEuM7rX9frgyHoZkWqx4iKu1spD",
        #                 "ts": "1597026383085",
        #                 "state": "2",
        #                 "depId": "4703779"
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_transactions(data, currency, since, limit, params)

    async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {
            # 'ccy': currency['id'],
            # 'state': 2,  # -3: pending cancel, -2 canceled, -1 failed, 0, pending, 1 sending, 2 sent, 3 awaiting email verification, 4 awaiting manual verification, 5 awaiting identity verification
            # 'after': since,
            # 'before': self.milliseconds(),
            # 'limit': limit,  # default 100, max 100
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['ccy'] = 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
        response = await self.privateGetAssetWithdrawalHistory(self.extend(request, params))
        #
        #     {
        #         "code": "0",
        #         "msg": "",
        #         "data": [
        #             {
        #                 "amt": "0.094",
        #                 "wdId": "4703879",
        #                 "fee": "0.01000000eth",
        #                 "txId": "0x62477bac6509a04512819bb1455e923a60dea5966c7caeaa0b24eb8fb0432b85",
        #                 "ccy": "ETH",
        #                 "from": "13426335357",
        #                 "to": "0xA41446125D0B5b6785f6898c9D67874D763A1519",
        #                 "ts": "1597026383085",
        #                 "state": "2"
        #             },
        #             {
        #                 "amt": "0.01",
        #                 "wdId": "4703879",
        #                 "fee": "0.00000000btc",
        #                 "txId": "",
        #                 "ccy": "BTC",
        #                 "from": "13426335357",
        #                 "to": "13426335357",
        #                 "ts": "1597026383085",
        #                 "state": "2"
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_transactions(data, currency, since, limit, params)

    def parse_transaction_status(self, status):
        #
        # deposit statuses
        #
        #     {
        #         '0': 'waiting for confirmation',
        #         '1': 'deposit credited',
        #         '2': 'deposit successful'
        #     }
        #
        # withdrawal statuses
        #
        #     {
        #        '-3': 'pending cancel',
        #        '-2': 'canceled',
        #        '-1': 'failed',
        #         '0': 'pending',
        #         '1': 'sending',
        #         '2': 'sent',
        #         '3': 'awaiting email verification',
        #         '4': 'awaiting manual verification',
        #         '5': 'awaiting identity verification'
        #     }
        #
        statuses = {
            '-3': 'pending',
            '-2': 'canceled',
            '-1': 'failed',
            '0': 'pending',
            '1': 'pending',
            '2': 'ok',
            '3': 'pending',
            '4': 'pending',
            '5': 'pending',
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction, currency=None):
        #
        # withdraw
        #
        #     {
        #         "amt": "0.1",
        #         "wdId": "67485",
        #         "ccy": "BTC"
        #     }
        #
        # fetchWithdrawals
        #
        #     {
        #         "amt": "0.094",
        #         "wdId": "4703879",
        #         "fee": "0.01000000eth",
        #         "txId": "0x62477bac6509a04512819bb1455e923a60dea5966c7caeaa0b24eb8fb0432b85",
        #         "ccy": "ETH",
        #         "from": "13426335357",
        #         "to": "0xA41446125D0B5b6785f6898c9D67874D763A1519",
        #         'tag': string,
        #         'pmtId': string,
        #         'memo': string,
        #         "ts": "1597026383085",
        #         "state": "2"
        #     }
        #
        # fetchDeposits
        #
        #     {
        #         "amt": "0.01044408",
        #         "txId": "1915737_3_0_0_asset",
        #         "ccy": "BTC",
        #         "from": "13801825426",
        #         "to": "",
        #         "ts": "1597026383085",
        #         "state": "2",
        #         "depId": "4703879"
        #     }
        #
        type = None
        id = None
        withdrawalId = self.safe_string(transaction, 'wdId')
        addressFrom = self.safe_string(transaction, 'from')
        addressTo = self.safe_string(transaction, 'to')
        address = addressTo
        tagTo = self.safe_string_2(transaction, 'tag', 'memo')
        tagTo = self.safe_string_2(transaction, 'pmtId', tagTo)
        if withdrawalId is not None:
            type = 'withdrawal'
            id = withdrawalId
        else:
            # the payment_id will appear on new deposits but appears to be removed from the response after 2 months
            id = self.safe_string(transaction, 'depId')
            type = 'deposit'
        currencyId = self.safe_string(transaction, 'ccy')
        code = self.safe_currency_code(currencyId)
        amount = self.safe_number(transaction, 'amt')
        status = self.parse_transaction_status(self.safe_string(transaction, 'state'))
        txid = self.safe_string(transaction, 'txId')
        timestamp = self.safe_integer(transaction, 'ts')
        feeCost = None
        if type == 'deposit':
            feeCost = 0
        else:
            feeCost = self.safe_number(transaction, 'fee')
        # todo parse tags
        return {
            'info': transaction,
            'id': id,
            'currency': code,
            'amount': amount,
            'network': None,
            'addressFrom': addressFrom,
            'addressTo': addressTo,
            'address': address,
            'tagFrom': None,
            'tagTo': tagTo,
            'tag': tagTo,
            'status': status,
            'type': type,
            'updated': None,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': {
                'currency': code,
                'cost': feeCost,
            },
        }

    async def fetch_leverage(self, symbol, params={}):
        await self.load_markets()
        marginMode = self.safe_string_lower(params, 'mgnMode')
        params = self.omit(params, ['mgnMode'])
        if (marginMode != 'cross') and (marginMode != 'isolated'):
            raise BadRequest(self.id + ' setLeverage params["mgnMode"] must be either "cross" or "isolated"')
        market = self.market(symbol)
        request = {
            'instId': market['id'],
            'mgnMode': marginMode,
        }
        response = await self.privateGetAccountLeverageInfo(self.extend(request, params))
        #
        #     {
        #       "code": "0",
        #       "data": [
        #         {
        #           "instId": "BTC-USDT-SWAP",
        #           "lever": "5.00000000",
        #           "mgnMode": "isolated",
        #           "posSide": "net"
        #         }
        #       ],
        #       "msg": ""
        #     }
        #
        return response

    async def fetch_position(self, symbol, params={}):
        await self.load_markets()
        market = self.market(symbol)
        type = self.safe_string(params, 'type')
        params = self.omit(params, 'type')
        request = {
            # instType String No Instrument type, MARGIN, SWAP, FUTURES, OPTION
            'instId': market['id'],
            # posId String No Single position ID or multiple position IDs(no more than 20) separated with comma
        }
        if type is not None:
            request['instType'] = type.upper()
        params = self.omit(params, 'type')
        response = await self.privateGetAccountPositions(params)
        #
        #     {
        #         "code": "0",
        #         "msg": "",
        #         "data": [
        #             {
        #                 "adl":"1",
        #                 "availPos":"1",
        #                 "avgPx":"2566.31",
        #                 "cTime":"1619507758793",
        #                 "ccy":"ETH",
        #                 "deltaBS":"",
        #                 "deltaPA":"",
        #                 "gammaBS":"",
        #                 "gammaPA":"",
        #                 "imr":"",
        #                 "instId":"ETH-USD-210430",
        #                 "instType":"FUTURES",
        #                 "interest":"0",
        #                 "last":"2566.22",
        #                 "lever":"10",
        #                 "liab":"",
        #                 "liabCcy":"",
        #                 "liqPx":"2352.8496681818233",
        #                 "margin":"0.0003896645377994",
        #                 "mgnMode":"isolated",
        #                 "mgnRatio":"11.731726509588816",
        #                 "mmr":"0.0000311811092368",
        #                 "optVal":"",
        #                 "pTime":"1619507761462",
        #                 "pos":"1",
        #                 "posCcy":"",
        #                 "posId":"307173036051017730",
        #                 "posSide":"long",
        #                 "thetaBS":"",
        #                 "thetaPA":"",
        #                 "tradeId":"109844",
        #                 "uTime":"1619507761462",
        #                 "upl":"-0.0000009932766034",
        #                 "uplRatio":"-0.0025490556801078",
        #                 "vegaBS":"",
        #                 "vegaPA":""
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        position = self.safe_value(data, 0)
        if position is None:
            return position
        return self.parse_position(position)

    async def fetch_positions(self, symbols=None, params={}):
        await self.load_markets()
        # defaultType = self.safe_string_2(self.options, 'fetchPositions', 'defaultType')
        # type = self.safe_string(params, 'type', defaultType)
        type = self.safe_string(params, 'type')
        params = self.omit(params, 'type')
        request = {
            # instType String No Instrument type, MARGIN, SWAP, FUTURES, OPTION, instId will be checked against instType when both parameters are passed, and the position information of the instId will be returned.
            # instId String No Instrument ID, e.g. BTC-USD-190927-5000-C
            # posId String No Single position ID or multiple position IDs(no more than 20) separated with comma
        }
        if type is not None:
            request['instType'] = type.upper()
        params = self.omit(params, 'type')
        response = await self.privateGetAccountPositions(self.extend(request, params))
        #
        #     {
        #         "code": "0",
        #         "msg": "",
        #         "data": [
        #             {
        #                 "adl":"1",
        #                 "availPos":"1",
        #                 "avgPx":"2566.31",
        #                 "cTime":"1619507758793",
        #                 "ccy":"ETH",
        #                 "deltaBS":"",
        #                 "deltaPA":"",
        #                 "gammaBS":"",
        #                 "gammaPA":"",
        #                 "imr":"",
        #                 "instId":"ETH-USD-210430",
        #                 "instType":"FUTURES",
        #                 "interest":"0",
        #                 "last":"2566.22",
        #                 "lever":"10",
        #                 "liab":"",
        #                 "liabCcy":"",
        #                 "liqPx":"2352.8496681818233",
        #                 "margin":"0.0003896645377994",
        #                 "mgnMode":"isolated",
        #                 "mgnRatio":"11.731726509588816",
        #                 "mmr":"0.0000311811092368",
        #                 "optVal":"",
        #                 "pTime":"1619507761462",
        #                 "pos":"1",
        #                 "posCcy":"",
        #                 "posId":"307173036051017730",
        #                 "posSide":"long",
        #                 "thetaBS":"",
        #                 "thetaPA":"",
        #                 "tradeId":"109844",
        #                 "uTime":"1619507761462",
        #                 "upl":"-0.0000009932766034",
        #                 "uplRatio":"-0.0025490556801078",
        #                 "vegaBS":"",
        #                 "vegaPA":""
        #             }
        #         ]
        #     }
        #
        positions = self.safe_value(response, 'data', [])
        result = []
        for i in range(0, len(positions)):
            entry = positions[i]
            instrument = self.safe_string(entry, 'instType')
            if (instrument == 'FUTURES') or instrument == ('SWAP'):
                result.append(self.parse_position(positions[i]))
        return result

    def parse_position(self, position, market=None):
        #
        #     {
        #       "adl": "3",
        #       "availPos": "1",
        #       "avgPx": "34131.1",
        #       "cTime": "1627227626502",
        #       "ccy": "USDT",
        #       "deltaBS": "",
        #       "deltaPA": "",
        #       "gammaBS": "",
        #       "gammaPA": "",
        #       "imr": "170.66093041794787",
        #       "instId": "BTC-USDT-SWAP",
        #       "instType": "SWAP",
        #       "interest": "0",
        #       "last": "34134.4",
        #       "lever": "2",
        #       "liab": "",
        #       "liabCcy": "",
        #       "liqPx": "12608.959083877446",
        #       "markPx": "4786.459271773621",
        #       "margin": "",
        #       "mgnMode": "cross",
        #       "mgnRatio": "140.49930117599155",
        #       "mmr": "1.3652874433435829",
        #       "notionalUsd": "341.5130010779638",
        #       "optVal": "",
        #       "pos": "1",
        #       "posCcy": "",
        #       "posId": "339552508062380036",
        #       "posSide": "long",
        #       "thetaBS": "",
        #       "thetaPA": "",
        #       "tradeId": "98617799",
        #       "uTime": "1627227626502",
        #       "upl": "0.0108608358957281",
        #       "uplRatio": "0.0000636418743944",
        #       "vegaBS": "",
        #       "vegaPA": ""
        #     }
        #
        marketId = self.safe_string(position, 'instId')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        contractsString = self.safe_string(position, 'pos')
        contractsAbs = Precise.string_abs(contractsString)
        contracts = None
        side = self.safe_string(position, 'posSide')
        hedged = side != 'net'
        if contractsString is not None:
            contracts = self.parse_number(contractsAbs)
            if side == 'net':
                if Precise.string_gt(contractsString, '0'):
                    side = 'long'
                else:
                    side = 'short'
        markPriceString = self.safe_string(position, 'markPx')
        notionalString = self.safe_string(position, 'notionalUsd')
        if market['inverse']:
            notionalString = Precise.string_div(notionalString, markPriceString)
        notional = self.parse_number(notionalString)
        marginType = self.safe_string(position, 'mgnMode')
        initialMarginString = None
        entryPriceString = self.safe_string(position, 'avgPx')
        unrealizedPnlString = self.safe_string(position, 'upl')
        leverageString = self.safe_string(position, 'lever')
        initialMarginPercentage = None
        collateralString = None
        if marginType == 'cross':
            initialMarginString = self.safe_string(position, 'imr')
            collateralString = Precise.string_add(initialMarginString, unrealizedPnlString)
        elif marginType == 'isolated':
            initialMarginPercentage = Precise.string_div('1', leverageString)
            collateralString = self.safe_string(position, 'margin')
        maintenanceMarginString = self.safe_string(position, 'mmr')
        maintenanceMargin = self.parse_number(maintenanceMarginString)
        maintenanceMarginPercentage = 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(maintenanceMarginPercentage, rounder), '1', 4))
        liquidationPrice = self.safe_number(position, 'liqPx')
        percentageString = self.safe_string(position, 'uplRatio')
        percentage = self.parse_number(Precise.string_mul(percentageString, '100'))
        timestamp = self.safe_integer(position, 'uTime')
        marginRatio = self.parse_number(Precise.string_div(maintenanceMarginString, collateralString, 4))
        return {
            'info': position,
            'symbol': symbol,
            'notional': notional,
            'marginType': marginType,
            'liquidationPrice': liquidationPrice,
            'entryPrice': self.parse_number(entryPriceString),
            'unrealizedPnl': self.parse_number(unrealizedPnlString),
            'percentage': percentage,
            'contracts': contracts,
            'contractSize': self.safe_value(market, 'contractSize'),
            'markPrice': self.parse_number(markPriceString),
            'side': side,
            'hedged': hedged,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            '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,
        }

    async def transfer(self, code, amount, fromAccount, toAccount, params={}):
        await self.load_markets()
        currency = self.currency(code)
        accountsByType = self.safe_value(self.options, 'accountsByType', {})
        fromId = self.safe_string(accountsByType, fromAccount)
        toId = self.safe_string(accountsByType, toAccount)
        if fromId is None:
            keys = list(accountsByType.keys())
            raise ExchangeError(self.id + ' fromAccount must be one of ' + ', '.join(keys))
        if toId is None:
            keys = list(accountsByType.keys())
            raise ExchangeError(self.id + ' toAccount must be one of ' + ', '.join(keys))
        request = {
            'ccy': currency['id'],
            'amt': self.currency_to_precision(code, amount),
            'type': '0',  # 0 = transfer within account by default, 1 = master account to sub-account, 2 = sub-account to master account
            'from': fromId,  # remitting account, 1 = SPOT, 3 = FUTURES, 5 = MARGIN, 6 = FUNDING, 9 = SWAP, 12 = OPTION, 18 = Unified account
            'to': toId,  # beneficiary account, 1 = SPOT, 3 = FUTURES, 5 = MARGIN, 6 = FUNDING, 9 = SWAP, 12 = OPTION, 18 = Unified account
            # 'subAcct': 'sub-account-name',  # optional, only required when type is 1 or 2
            # 'instId': market['id'],  # required when from is 3, 5 or 9, margin trading pair like BTC-USDT or contract underlying like BTC-USD to be transferred out
            # 'toInstId': market['id'],  # required when from is 3, 5 or 9, margin trading pair like BTC-USDT or contract underlying like BTC-USD to be transferred in
        }
        response = await self.privatePostAssetTransfer(self.extend(request, params))
        #
        #     {
        #         "code": "0",
        #         "msg": "",
        #         "data": [
        #             {
        #                 "transId": "754147",
        #                 "ccy": "USDT",
        #                 "from": "6",
        #                 "amt": "0.1",
        #                 "to": "18"
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        rawTransfer = self.safe_value(data, 0, {})
        return self.parse_transfer(rawTransfer, currency)

    def parse_transfer(self, transfer, currency=None):
        #
        # transfer
        #
        #     {
        #         "transId": "754147",
        #         "ccy": "USDT",
        #         "from": "6",
        #         "amt": "0.1",
        #         "to": "18"
        #     }
        #
        id = self.safe_string(transfer, 'transId')
        currencyId = self.safe_string(transfer, 'ccy')
        code = self.safe_currency_code(currencyId, currency)
        amount = self.safe_number(transfer, 'amt')
        fromAccountId = self.safe_string(transfer, 'from')
        toAccountId = self.safe_string(transfer, 'to')
        typesByAccount = self.safe_value(self.options, 'typesByAccount', {})
        fromAccount = self.safe_string(typesByAccount, fromAccountId)
        toAccount = self.safe_string(typesByAccount, toAccountId)
        timestamp = None
        status = None
        return {
            'info': transfer,
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'currency': code,
            'amount': amount,
            'fromAccount': fromAccount,
            'toAccount': toAccount,
            'status': status,
        }

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        isArray = isinstance(params, list)
        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 query:
                url += '?' + self.urlencode(query)
        elif api == 'private':
            self.check_required_credentials()
            timestamp = self.iso8601(self.milliseconds())
            headers = {
                'OK-ACCESS-KEY': self.apiKey,
                'OK-ACCESS-PASSPHRASE': self.password,
                'OK-ACCESS-TIMESTAMP': timestamp,
                # 'OK-FROM': '',
                # 'OK-TO': '',
                # 'OK-LIMIT': '',
            }
            auth = timestamp + method + request
            if method == 'GET':
                if query:
                    urlencodedQuery = '?' + self.urlencode(query)
                    url += urlencodedQuery
                    auth += urlencodedQuery
            else:
                if isArray or query:
                    body = self.json(query)
                    auth += body
                headers['Content-Type'] = 'application/json'
            signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256, 'base64')
            headers['OK-ACCESS-SIGN'] = signature
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def parse_funding_rate(self, fundingRate, market=None):
        #
        #     {
        #       "fundingRate": "0.00027815",
        #       "fundingTime": "1634256000000",
        #       "instId": "BTC-USD-SWAP",
        #       "instType": "SWAP",
        #       "nextFundingRate": "0.00017",
        #       "nextFundingTime": "1634284800000"
        #     }
        #
        # in the response above nextFundingRate is actually two funding rates from now
        #
        nextFundingRateTimestamp = self.safe_integer(fundingRate, 'fundingTime')
        marketId = self.safe_string(fundingRate, 'instId')
        symbol = self.safe_symbol(marketId, market)
        nextFundingRate = self.safe_number(fundingRate, 'fundingRate')
        # https://www.okex.com/support/hc/en-us/articles/360053909272-Ⅸ-Introduction-to-perpetual-swap-funding-fee
        # > The current interest is 0.
        return {
            'info': fundingRate,
            'symbol': symbol,
            'markPrice': None,
            'indexPrice': None,
            'interestRate': self.parse_number('0'),
            'estimatedSettlePrice': None,
            'timestamp': None,
            'datetime': None,
            'previousFundingRate': None,
            'nextFundingRate': nextFundingRate,
            'previousFundingTimestamp': None,
            'nextFundingTimestamp': nextFundingRateTimestamp,
            'previousFundingDatetime': None,
            'nextFundingDatetime': self.iso8601(nextFundingRateTimestamp),
        }

    async def fetch_funding_rate(self, symbol, params={}):
        await self.load_markets()
        market = self.market(symbol)
        if not market['swap']:
            raise ExchangeError(self.id + ' fetchFundingRate is only valid for swap markets')
        request = {
            'instId': market['id'],
        }
        response = await self.publicGetPublicFundingRate(self.extend(request, params))
        #
        #     {
        #       "code": "0",
        #       "data": [
        #         {
        #           "fundingRate": "0.00027815",
        #           "fundingTime": "1634256000000",
        #           "instId": "BTC-USD-SWAP",
        #           "instType": "SWAP",
        #           "nextFundingRate": "0.00017",
        #           "nextFundingTime": "1634284800000"
        #         }
        #       ],
        #       "msg": ""
        #     }
        #
        data = self.safe_value(response, 'data', [])
        entry = self.safe_value(data, 0, {})
        return self.parse_funding_rate(entry, market)

    async def fetch_funding_history(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {
            # 'instType': 'SPOT',  # SPOT, MARGIN, SWAP, FUTURES, OPTION
            # 'ccy': currency['id'],
            # 'mgnMode': 'isolated',  # isolated, cross
            # 'ctType': 'linear',  # linear, inverse, only applicable to FUTURES/SWAP
            'type': '8',
            #
            # supported values for type
            #
            #     1 Transfer
            #     2 Trade
            #     3 Delivery
            #     4 Auto token conversion
            #     5 Liquidation
            #     6 Margin transfer
            #     7 Interest deduction
            #     8 Funding fee
            #     9 ADL
            #     10 Clawback
            #     11 System token conversion
            #     12 Strategy transfer
            #     13 ddh
            #
            # 'subType': '',
            #
            # supported values for subType
            #
            #     1 Buy
            #     2 Sell
            #     3 Open long
            #     4 Open short
            #     5 Close long
            #     6 Close short
            #     9 Interest deduction
            #     11 Transfer in
            #     12 Transfer out
            #     160 Manual margin increase
            #     161 Manual margin decrease
            #     162 Auto margin increase
            #     110 Auto buy
            #     111 Auto sell
            #     118 System token conversion transfer in
            #     119 System token conversion transfer out
            #     100 Partial liquidation close long
            #     101 Partial liquidation close short
            #     102 Partial liquidation buy
            #     103 Partial liquidation sell
            #     104 Liquidation long
            #     105 Liquidation short
            #     106 Liquidation buy
            #     107 Liquidation sell
            #     110 Liquidation transfer in
            #     111 Liquidation transfer out
            #     125 ADL close long
            #     126 ADL close short
            #     127 ADL buy
            #     128 ADL sell
            #     131 ddh buy
            #     132 ddh sell
            #     170 Exercised
            #     171 Counterparty exercised
            #     172 Expired OTM
            #     112 Delivery long
            #     113 Delivery short
            #     117 Delivery/Exercise clawback
            #     173 Funding fee expense
            #     174 Funding fee income
            #     200 System transfer in
            #     201 Manually transfer in
            #     202 System transfer out
            #     203 Manually transfer out
            #
            # 'after': 'id',  # earlier than the requested bill ID
            # 'before': 'id',  # newer than the requested bill ID
            # 'limit': '100',  # default 100, max 100
        }
        if limit is not None:
            request['limit'] = str(limit)  # default 100, max 100
        response = await self.privateGetAccountBills(self.extend(request, params))
        #
        #     {
        #       "bal": "0.0242946200998573",
        #       "balChg": "0.0000148752712240",
        #       "billId": "377970609204146187",
        #       "ccy": "ETH",
        #       "execType": "",
        #       "fee": "0",
        #       "from": "",
        #       "instId": "ETH-USD-SWAP",
        #       "instType": "SWAP",
        #       "mgnMode": "isolated",
        #       "notes": "",
        #       "ordId": "",
        #       "pnl": "0.000014875271224",
        #       "posBal": "0",
        #       "posBalChg": "0",
        #       "subType": "174",
        #       "sz": "9",
        #       "to": "",
        #       "ts": "1636387215588",
        #       "type": "8"
        #     }
        #
        data = self.safe_value(response, 'data')
        result = []
        for i in range(0, len(data)):
            entry = data[i]
            timestamp = self.safe_integer(entry, 'ts')
            instId = self.safe_string(entry, 'instId')
            market = self.safe_market(instId)
            result.append({
                'info': entry,
                'symbol': market['symbol'],
                'code': market['base'] if market['inverse'] else market['quote'],
                'timestamp': timestamp,
                'datetime': self.iso8601(timestamp),
                'id': self.safe_string(entry, 'billId'),
                'amount': self.safe_number(entry, 'balChg'),
            })
        sorted = self.sort_by(result, 'timestamp')
        return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)

    async def set_leverage(self, leverage, symbol=None, params={}):
        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 = self.safe_string_lower(params, 'mgnMode')
        params = self.omit(params, ['mgnMode'])
        if (marginMode != 'cross') and (marginMode != 'isolated'):
            raise BadRequest(self.id + ' setLeverage params["mgnMode"] must be either "cross" or "isolated"')
        request = {
            'lever': leverage,
            'mgnMode': marginMode,
            'instId': market['id'],
        }
        response = await self.privatePostAccountSetLeverage(self.extend(request, params))
        #
        #     {
        #       "code": "0",
        #       "data": [
        #         {
        #           "instId": "BTC-USDT-SWAP",
        #           "lever": "5",
        #           "mgnMode": "isolated",
        #           "posSide": "long"
        #         }
        #       ],
        #       "msg": ""
        #     }
        #
        return response

    async def set_position_mode(self, hedged, symbol=None, params={}):
        hedgeMode = None
        if hedged:
            hedgeMode = 'long_short_mode'
        else:
            hedgeMode = 'net_mode'
        request = {
            'posMode': hedgeMode,
        }
        response = await self.privatePostAccountSetPositionMode(self.extend(request, params))
        #
        #     {
        #       "code": "0",
        #       "data": [
        #         {
        #           "posMode": "net_mode"
        #         }
        #       ],
        #       "msg": ""
        #     }
        #
        return response

    async def set_margin_mode(self, marginType, symbol=None, params={}):
        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 (marginType != 'cross') and (marginType != 'isolated'):
            raise BadRequest(self.id + ' setMarginMode marginType must be either "cross" or "isolated"')
        await self.load_markets()
        market = self.market(symbol)
        lever = self.safe_integer(params, 'lever')
        if (lever is None) or (lever < 1) or (lever > 125):
            raise BadRequest(self.id + ' setMarginMode params["lever"] should be between 1 and 125')
        params = self.omit(params, ['lever'])
        request = {
            'lever': lever,
            'mgnMode': marginType,
            'instId': market['id'],
        }
        response = await self.privatePostAccountSetLeverage(self.extend(request, params))
        #
        #     {
        #       "code": "0",
        #       "data": [
        #         {
        #           "instId": "BTC-USDT-SWAP",
        #           "lever": "5",
        #           "mgnMode": "isolated",
        #           "posSide": "long"
        #         }
        #       ],
        #       "msg": ""
        #     }
        #
        return response

    async def modify_margin_helper(self, symbol, amount, type, params={}):
        await self.load_markets()
        market = self.market(symbol)
        posSide = self.safe_string(params, 'posSide', 'net')
        params = self.omit(params, ['posSide'])
        request = {
            'instId': market['id'],
            'amt': amount,
            'type': type,
            'posSide': posSide,
        }
        response = await self.privatePostAccountPositionMarginBalance(self.extend(request, params))
        #
        #     {
        #       "code": "0",
        #       "data": [
        #         {
        #           "amt": "0.01",
        #           "instId": "ETH-USD-SWAP",
        #           "posSide": "net",
        #           "type": "reduce"
        #         }
        #       ],
        #       "msg": ""
        #     }
        #
        data = self.safe_value(response, 'data', [])
        entry = self.safe_value(data, 0, {})
        errorCode = self.safe_string(response, 'code')
        status = 'ok' if (errorCode == '0') else 'failed'
        responseAmount = self.safe_number(entry, 'amt')
        responseType = self.safe_string(entry, 'type')
        marketId = self.safe_string(entry, 'instId')
        responseMarket = self.safe_market(marketId, market)
        code = responseMarket['base'] if responseMarket['inverse'] else responseMarket['quote']
        symbol = responseMarket['symbol']
        return {
            'info': response,
            'type': responseType,
            'amount': responseAmount,
            'code': code,
            'symbol': symbol,
            'status': status,
        }

    async def fetch_borrow_rates(self, params={}):
        await self.load_markets()
        response = await self.privateGetAccountInterestRate(params)
        # {
        #     "code": "0",
        #     "data": [
        #         {
        #             "ccy":"BTC",
        #             "interestRate":"0.00000833"
        #         }
        #         ...
        #     ],
        # }
        timestamp = self.milliseconds()
        data = self.safe_value(response, 'data')
        rates = {}
        for i in range(0, len(data)):
            rate = data[i]
            code = self.safe_currency_code(self.safe_string(rate, 'ccy'))
            rates[code] = {
                'currency': code,
                'rate': self.safe_number(rate, 'interestRate'),
                'period': 86400000,
                'timestamp': timestamp,
                'datetime': self.iso8601(timestamp),
                'info': rate,
            }
        return rates

    async def fetch_borrow_rate(self, code, params={}):
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'ccy': currency['id'],
        }
        response = await self.privateGetAccountInterestRate(self.extend(request, params))
        # {
        #     "code": "0",
        #     "data":[
        #          {
        #             "ccy":"USDT",
        #             "interestRate":"0.00002065"
        #          }
        #          ...
        #     ],
        #     "msg":""
        # }
        timestamp = self.milliseconds()
        data = self.safe_value(response, 'data')
        rate = self.safe_value(data, 0)
        return {
            'currency': code,
            'rate': self.safe_number(rate, 'interestRate'),
            'period': 86400000,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'info': rate,
        }

    async def reduce_margin(self, symbol, amount, params={}):
        return await self.modify_margin_helper(symbol, amount, 'reduce', params)

    async def add_margin(self, symbol, amount, params={}):
        return await self.modify_margin_helper(symbol, amount, 'add', params)

    def set_sandbox_mode(self, enable):
        if enable:
            self.headers['x-simulated-trading'] = 1
        elif 'x-simulated-trading' in self.headers:
            self.headers = self.omit(self.headers, 'x-simulated-trading')

    def handle_errors(self, httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if not response:
            return  # fallback to default error handler
        #
        #     {"code":"1","data":[{"clOrdId":"","ordId":"","sCode":"51119","sMsg":"Order placement failed due to insufficient balance. ","tag":""}],"msg":""}
        #     {"code":"58001","data":[],"msg":"Incorrect trade password"}
        #
        code = self.safe_string(response, 'code')
        if code != '0':
            feedback = self.id + ' ' + body
            data = self.safe_value(response, 'data', [])
            for i in range(0, len(data)):
                error = data[i]
                errorCode = self.safe_string(error, 'sCode')
                message = self.safe_string(error, 'sMsg')
                self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
                self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
            self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
            raise ExchangeError(feedback)  # unknown message
