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

# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code

from ccxt.async_support.base.exchange import Exchange
from ccxt.abstract.okcoin import ImplicitAPI
import hashlib
from ccxt.base.types import Balances, Currencies, Currency, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, Transaction, TransferEntry
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import AccountNotEnabled
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 TICK_SIZE
from ccxt.base.precise import Precise


class okcoin(Exchange, ImplicitAPI):

    def describe(self):
        return self.deep_extend(super(okcoin, self).describe(), {
            'id': 'okcoin',
            'name': 'OKCoin',
            'countries': ['CN', 'US'],
            'version': 'v5',
            # cheapest endpoint is 100 requests per 2 seconds
            # 50 requests per second => 1000 / 50 = 20ms
            'rateLimit': 20,
            'pro': True,
            'has': {
                'CORS': None,
                'spot': True,
                'margin': False,
                'swap': False,
                'future': True,
                'option': None,
                'cancelOrder': True,
                'createMarketBuyOrderWithCost': True,
                'createMarketOrderWithCost': False,
                'createMarketSellOrderWithCost': False,
                'createOrder': True,
                'fetchBalance': True,
                'fetchBorrowInterest': False,
                'fetchBorrowRate': False,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchBorrowRates': False,
                'fetchBorrowRatesPerSymbol': False,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,  # see below
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchLedger': True,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': None,
                'fetchOrderTrades': True,
                'fetchPosition': False,
                'fetchPositions': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTransactions': None,
                'fetchWithdrawals': True,
                'reduceMargin': False,
                'repayCrossMargin': False,
                'repayIsolatedMargin': False,
                'setMargin': False,
                'transfer': True,
                'withdraw': True,
            },
            'timeframes': {
                '1m': '1m',
                '3m': '3m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1H',
                '2h': '2H',
                '4h': '4H',
                '6h': '6H',
                '12h': '12H',
                '1d': '1D',
                '1w': '1W',
                '1M': '1M',
                '3M': '3M',
            },
            'hostname': 'okcoin.com',
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/51840849/87295551-102fbf00-c50e-11ea-90a9-462eebba5829.jpg',
                'api': {
                    'rest': 'https://www.{hostname}',
                },
                'www': 'https://www.okcoin.com',
                'doc': 'https://www.okcoin.com/docs/en/',
                'fees': 'https://www.okcoin.com/coin-fees',
                'referral': 'https://www.okcoin.com/account/register?flag=activity&channelId=600001513',
                'test': {
                    'rest': 'https://testnet.okex.com',
                },
            },
            'api': {
                'public': {
                    'get': {
                        'market/tickers': 1,
                        'market/ticker': 1,
                        'market/books': 1 / 2,
                        'market/candles': 1 / 2,
                        'market/history-candles': 1 / 2,
                        'market/trades': 1 / 5,
                        'market/history-trades': 2,
                        'market/platform-24-volume': 10,
                        'market/open-oracle': 50,
                        'market/exchange-rate': 20,
                        'public/instruments': 1,
                        'public/time': 2,
                    },
                },
                'private': {
                    'get': {
                        # trade
                        'trade/order': 1 / 3,
                        'trade/orders-pending': 1 / 3,
                        'trade/orders-history': 1 / 2,
                        'trade/orders-history-archive': 1 / 2,
                        'trade/fills': 1 / 3,
                        'trade/fills-history': 2.2,
                        'trade/fills-archive': 2,
                        'trade/order-algo': 1,
                        'trade/orders-algo-pending': 1,
                        'trade/orders-algo-history': 1,
                        # rfq
                        'otc/rfq/trade': 4,
                        'otc/rfq/history': 4,
                        # account
                        'account/balance': 2,
                        'account/bills': 5 / 3,
                        'account/bills-archive': 5 / 3,
                        'account/config': 4,
                        'account/max-size': 4,
                        'account/max-avail-size': 4,
                        'account/trade-fee': 4,
                        'account/max-withdrawal': 4,
                        # funding or assets
                        'asset/currencies': 5 / 3,
                        'asset/balances': 5 / 3,
                        'asset/asset-valuation': 10,
                        'asset/transfer-state': 10,
                        'asset/bills': 5 / 3,
                        'asset/deposit-lightning': 5,
                        'asset/deposit-address': 5 / 3,
                        'asset/deposit-history': 5 / 3,
                        'asset/withdrawal-history': 5 / 3,
                        'asset/deposit-withdraw-status': 20,
                        # fiat
                        'fiat/deposit-history': 5 / 3,
                        'fiat-withdraw-history': 5 / 3,
                        'fiat-channel': 5 / 3,
                        # sub-account
                        'users/subaccount/list': 10,
                        'users/subaccount/apiKey': 10,
                        'account/subaccount/balances': 10,
                        'asset/subaccount/balances': 10,
                        'asset/subaccount/bills': 10,
                    },
                    'post': {
                        # trade
                        '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 / 150,
                        'trade/order-algo': 1,
                        'trade/cancel-algos': 1,
                        'trade/cancel-advance-algos': 1,
                        # rfq
                        'otc/rfq/quote': 4,
                        'otc/rfq/trade': 4,
                        # funding
                        'asset/transfer': 4,
                        'asset/withdrawal': 4,
                        'asset/withdrawal-lightning': 4,
                        'asset/withdrawal-cancel': 4,
                        # fiat
                        'fiat/deposit': 5 / 3,
                        'fiat/cancel-deposit': 5 / 3,
                        'fiat/withdrawal': 5 / 3,
                        'fiat/cancel-withdrawal': 5 / 3,
                        # sub-account
                        'asset/subaccount/transfer': 10,
                    },
                },
            },
            'fees': {
                'trading': {
                    'taker': 0.002,
                    'maker': 0.001,
                },
                'spot': {
                    'taker': 0.0015,
                    'maker': 0.0010,
                },
            },
            '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': BadRequest,  # 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': BadRequest,  # Parameter {0} and {1} can not exist at the same time
                    '50025': ExchangeError,  # Parameter {0} count exceeds the limit {1}
                    '50026': ExchangeNotAvailable,  # System error, please try again later.
                    '50027': PermissionDenied,  # The account is restricted from trading
                    '50028': ExchangeError,  # Unable to take the order, please reach out to support center for details
                    '50029': ExchangeError,  # This instrument({0}) is unavailable at present due to risk management. Please contact customer service for help.
                    '50030': PermissionDenied,  # No permission to use self API
                    '50032': AccountSuspended,  # This asset is blocked, allow its trading and try again
                    '50033': AccountSuspended,  # This instrument is blocked, allow its trading and try again
                    '50035': BadRequest,  # This endpoint requires that APIKey must be bound to IP
                    '50036': BadRequest,  # Invalid expTime
                    '50037': BadRequest,  # Order expired
                    '50038': ExchangeError,  # This feature is temporarily unavailable in demo trading
                    '50039': ExchangeError,  # The before parameter is not available for implementing timestamp pagination
                    '50041': ExchangeError,  # You are not currently on the whitelist, please contact customer service
                    '50044': BadRequest,  # Must select one broker type
                    # API Class
                    '50100': ExchangeError,  # API frozen, please contact customer service
                    '50101': AuthenticationError,  # 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': AccountNotEnabled,  # Account level too low {"code":"1","data":[{"clOrdId":"uJrfGFth9F","ordId":"","sCode":"51010","sMsg":"The current account mode does not support self API interface. ","tag":""}],"msg":"Operation failed."}
                    '51011': InvalidOrder,  # Duplicated order ID
                    '51012': BadSymbol,  # Token does not exist
                    '51014': BadSymbol,  # 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
                    '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
                    '51030': InvalidOrder,  # Funding fee is being settled.
                    '51031': InvalidOrder,  # This order price is not within the closing price range
                    '51032': InvalidOrder,  # Closing all positions at market price.
                    '51033': InvalidOrder,  # The total amount per order for self pair has reached the upper limit.
                    '51037': InvalidOrder,  # The current account risk status only supports you to place IOC orders that can reduce the risk of your account.
                    '51038': InvalidOrder,  # There is already an IOC order under the current risk module that reduces the risk of the account.
                    '51044': InvalidOrder,  # The order type {0}, {1} is not allowed to set stop loss and take profit
                    '51046': InvalidOrder,  # The take profit trigger price must be higher than the order price
                    '51047': InvalidOrder,  # The stop loss trigger price must be lower than the order price
                    '51048': InvalidOrder,  # The take profit trigger price should be lower than the order price
                    '51049': InvalidOrder,  # The stop loss trigger price should be higher than the order price
                    '51050': InvalidOrder,  # The take profit trigger price should be higher than the best ask price
                    '51051': InvalidOrder,  # The stop loss trigger price should be lower than the best ask price
                    '51052': InvalidOrder,  # The take profit trigger price should be lower than the best bid price
                    '51053': InvalidOrder,  # The stop loss trigger price should be higher than the best bid price
                    '51054': BadRequest,  # Getting information timed out, please try again later
                    '51056': InvalidOrder,  # Action not allowed
                    '51058': InvalidOrder,  # No available position for self algo order
                    '51059': InvalidOrder,  # Strategy for the current state does not support self operation
                    '51100': InvalidOrder,  # Trading amount does not meet the min tradable amount
                    '51102': InvalidOrder,  # Entered amount exceeds the max pending count
                    '51103': InvalidOrder,  # Entered amount exceeds the max pending order count of the underlying asset
                    '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
                    '51156': BadRequest,  # You're leading trades in long/short mode and can't use self API endpoint to close positions
                    '51159': BadRequest,  # You're leading trades in buy/sell mode. If you want to place orders using self API endpoint, the orders must be in the same direction existing positions and open orders.
                    '51162': InvalidOrder,  # You have {instrument} open orders. Cancel these orders and try again
                    '51163': InvalidOrder,  # You hold {instrument} positions. Close these positions and try again
                    '51166': InvalidOrder,  # Currently, we don't support leading trades with self instrument
                    '51174': InvalidOrder,  # The number of {param0} pending orders reached the upper limit of {param1}(orders).
                    '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
                    '51321': InvalidOrder,  # You're leading trades. Currently, we don't support leading trades with arbitrage, iceberg, or TWAP bots
                    '51322': InvalidOrder,  # You're leading trades that have been filled at market price. We've canceled your open stop orders to close your positions
                    '51323': BadRequest,  # You're already leading trades with take profit or stop loss settings. Cancel your existing stop orders to proceed
                    '51324': BadRequest,  # As a lead trader, you hold positions in {instrument}. To close your positions, place orders in the amount that equals the available amount for closing
                    '51325': InvalidOrder,  # As a lead trader, you must use market price when placing stop orders
                    '51327': InvalidOrder,  # closeFraction is only available for futures and perpetual swaps
                    '51328': InvalidOrder,  # closeFraction is only available for reduceOnly orders
                    '51329': InvalidOrder,  # closeFraction is only available in NET mode
                    '51330': InvalidOrder,  # closeFraction is only available for stop market orders
                    '51400': OrderNotFound,  # Cancellation failed order does not exist
                    '51401': OrderNotFound,  # Cancellation failed order is already canceled
                    '51402': OrderNotFound,  # Cancellation failed order is already completed
                    '51403': InvalidOrder,  # Cancellation failed order type does not support cancellation
                    '51404': InvalidOrder,  # Order cancellation unavailable during the second phase of call auction
                    '51405': ExchangeError,  # Cancellation failed 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 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 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 order has been canceled
                    '51510': ExchangeError,  # Modification failed order has been completed
                    '51511': ExchangeError,  # Modification failed 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
                    '51732': AuthenticationError,  # Required user KYC level not met
                    '51733': AuthenticationError,  # User is under risk control
                    '51734': AuthenticationError,  # User KYC Country is not supported
                    '51735': ExchangeError,  # Sub-account is not supported
                    '51736': InsufficientFunds,  # Insufficient {ccy} balance
                    # 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 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
                    '58125': BadRequest,  # Non-tradable assets can only be transferred from sub-accounts to main accounts
                    '58126': BadRequest,  # Non-tradable assets can only be transferred between funding accounts
                    '58127': BadRequest,  # Main account API Key does not support current transfer 'type' parameter. Please refer to the API documentation.
                    '58128': BadRequest,  # Sub-account API Key does not support current transfer 'type' parameter. Please refer to the API documentation.
                    '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
                    '58221': BadRequest,  # Missing label of withdrawal address.
                    '58222': BadRequest,  # Illegal withdrawal address.
                    '58224': BadRequest,  # This type of crypto does not support on-chain withdrawing to OKX addresses. Please withdraw through internal transfers.
                    '58227': BadRequest,  # Withdrawal of non-tradable assets can be withdrawn all at once only
                    '58228': BadRequest,  # Withdrawal of non-tradable assets requires that the API Key must be bound to an IP
                    '58229': InsufficientFunds,  # Insufficient funding account balance to pay fees {fee} USDT
                    '58300': ExchangeError,  # Deposit-address count exceeds the limit
                    '58350': InsufficientFunds,  # Insufficient balance
                    # Account error codes 59000-59999
                    '59000': ExchangeError,  # Your settings failed have positions or open orders
                    '59001': ExchangeError,  # Switching unavailable 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
                    '59128': InvalidOrder,  # As a lead trader, you can't lead trades in {instrument} with leverage higher than {num}
                    '59200': InsufficientFunds,  # Insufficient account balance
                    '59201': InsufficientFunds,  # Negative account balance
                    '59216': BadRequest,  # The position doesn't exist. Please try again
                    '59300': ExchangeError,  # Margin call failed. Position does not exist
                    '59301': ExchangeError,  # Margin adjustment failed for exceeding the max limit
                    '59313': ExchangeError,  # Unable to repay. You haven't borrowed any {ccy} {ccyPair} in Quick margin mode.
                    '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 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
                    '70010': BadRequest,  # Timestamp parameters need to be in Unix timestamp format in milliseconds.
                    '70013': BadRequest,  # endTs needs to be bigger than or equal to beginTs.
                    '70016': BadRequest,  # Please specify your instrument settings for at least one instType.
                },
                'broad': {
                    'Internal Server Error': ExchangeNotAvailable,  # {"code":500,"data":{},"detailMsg":"","error_code":"500","error_message":"Internal Server Error","msg":"Internal Server Error"}
                    'server error': ExchangeNotAvailable,  # {"code":500,"data":{},"detailMsg":"","error_code":"500","error_message":"server error 1236805249","msg":"server error 1236805249"}
                },
            },
            'precisionMode': TICK_SIZE,
            'options': {
                'fetchOHLCV': {
                    'type': 'Candles',  # Candles or HistoryCandles
                },
                'createMarketBuyOrderRequiresPrice': True,
                'fetchMarkets': ['spot'],
                'defaultType': 'spot',  # 'account', 'spot', 'futures', 'swap', 'option'
                'accountsByType': {
                    'classic': '1',
                    'spot': '1',
                    'funding': '6',
                    'main': '6',
                    'unified': '18',
                },
                'accountsById': {
                    '1': 'spot',
                    '6': 'funding',
                    '18': 'unified',
                },
                'auth': {
                    'time': 'public',
                    'currencies': 'private',
                    'instruments': 'public',
                    'rate': 'public',
                    '{instrument_id}/constituents': 'public',
                },
                'warnOnFetchCurrenciesWithoutAuthorization': False,
                'defaultNetwork': 'ERC20',
                'networks': {
                    'ERC20': 'Ethereum',
                },
            },
            '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_time(self, params={}):
        """
        fetches the current integer timestamp in milliseconds from the exchange server
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int: the current integer timestamp in milliseconds from the exchange server
        """
        response = await self.publicGetPublicTime(params)
        #
        #     {
        #         "iso": "2015-01-07T23:47:25.201Z",
        #         "epoch": 1420674445.201
        #     }
        #
        return self.parse8601(self.safe_string(response, 'iso'))

    async def fetch_markets(self, params={}) -> List[Market]:
        """
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-public-data-get-instruments
        retrieves data on all markets for okcoin
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        request: dict = {
            'instType': 'SPOT',
        }
        response = await self.publicGetPublicInstruments(self.extend(request, params))
        markets = self.safe_value(response, 'data', [])
        return self.parse_markets(markets)

    def parse_market(self, market: dict) -> Market:
        #
        # spot markets
        #
        #     {
        #         "base_currency": "EOS",
        #         "instrument_id": "EOS-OKB",
        #         "min_size": "0.01",
        #         "quote_currency": "OKB",
        #         "size_increment": "0.000001",
        #         "tick_size": "0.0001"
        #     }
        #
        id = self.safe_string(market, 'instId')
        type = self.safe_string_lower(market, 'instType')
        if type == 'futures':
            type = 'future'
        spot = (type == 'spot')
        future = (type == 'future')
        swap = (type == 'swap')
        option = (type == 'option')
        contract = swap or future or option
        baseId = self.safe_string(market, 'baseCcy')
        quoteId = self.safe_string(market, 'quoteCcy')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        symbol = base + '/' + quote
        tickSize = self.safe_string(market, 'tickSz')
        fees = self.safe_value_2(self.fees, type, 'trading', {})
        maxLeverage = self.safe_string(market, 'lever', '1')
        maxLeverage = Precise.string_max(maxLeverage, '1')
        maxSpotCost = self.safe_number(market, 'maxMktSz')
        return self.extend(fees, {
            'id': id,
            'symbol': symbol,
            'base': base,
            'quote': quote,
            'settle': None,
            'baseId': baseId,
            'quoteId': quoteId,
            'settleId': None,
            'type': type,
            'spot': spot,
            'margin': spot and (Precise.string_gt(maxLeverage, '1')),
            'swap': False,
            'future': False,
            'option': False,
            'active': True,
            'contract': False,
            'linear': None,
            'inverse': None,
            'contractSize': self.safe_number(market, 'ctVal') if contract else None,
            'expiry': None,
            'expiryDatetime': None,
            'strike': None,
            'optionType': None,
            'created': self.safe_integer(market, 'listTime'),
            'precision': {
                'amount': self.safe_number(market, 'lotSz'),
                'price': self.parse_number(tickSize),
            },
            'limits': {
                'leverage': {
                    'min': self.parse_number('1'),
                    'max': self.parse_number(maxLeverage),
                },
                'amount': {
                    'min': self.safe_number(market, 'minSz'),
                    'max': None,
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': None,
                    'max': None if contract else maxSpotCost,
                },
            },
            'info': market,
        })

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

    async def fetch_currencies(self, params={}) -> Currencies:
        """
        fetches all available currencies on an exchange
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an associative dictionary of currencies
        """
        if not self.check_required_credentials(False):
            if self.options['warnOnFetchCurrenciesWithoutAuthorization']:
                raise ExchangeError(self.id + ' fetchCurrencies() is a private API endpoint that requires authentication with API keys. Set the API keys on the exchange instance or exchange.options["warnOnFetchCurrenciesWithoutAuthorization"] = False to suppress self warning message.')
            return None
        else:
            response = await self.privateGetAssetCurrencies(params)
            data = self.safe_value(response, 'data', [])
            result: dict = {}
            dataByCurrencyId = self.group_by(data, 'ccy')
            currencyIds = list(dataByCurrencyId.keys())
            for i in range(0, len(currencyIds)):
                currencyId = currencyIds[i]
                currency = self.safe_currency(currencyId)
                code = currency['code']
                chains = dataByCurrencyId[currencyId]
                networks: dict = {}
                currencyActive = False
                depositEnabled = False
                withdrawEnabled = False
                maxPrecision = None
                for j in range(0, len(chains)):
                    chain = chains[j]
                    canDeposit = self.safe_value(chain, 'canDep')
                    depositEnabled = canDeposit if (canDeposit) else depositEnabled
                    canWithdraw = self.safe_value(chain, 'canWd')
                    withdrawEnabled = canWithdraw if (canWithdraw) else withdrawEnabled
                    canInternal = self.safe_value(chain, 'canInternal')
                    active = True if (canDeposit and canWithdraw and canInternal) else False
                    currencyActive = active if (active) else currencyActive
                    networkId = self.safe_string(chain, 'chain')
                    if (networkId is not None) and (networkId.find('-') >= 0):
                        parts = networkId.split('-')
                        chainPart = self.safe_string(parts, 1, networkId)
                        networkCode = self.safe_network(chainPart)
                        precision = self.parse_precision(self.safe_string(chain, 'wdTickSz'))
                        if maxPrecision is None:
                            maxPrecision = precision
                        else:
                            maxPrecision = Precise.string_min(maxPrecision, precision)
                        networks[networkCode] = {
                            'id': networkId,
                            'network': networkCode,
                            'active': active,
                            'deposit': canDeposit,
                            'withdraw': canWithdraw,
                            'fee': self.safe_number(chain, 'minFee'),
                            'precision': self.parse_number(precision),
                            'limits': {
                                'withdraw': {
                                    'min': self.safe_number(chain, 'minWd'),
                                    'max': self.safe_number(chain, 'maxWd'),
                                },
                            },
                            'info': chain,
                        }
                firstChain = self.safe_value(chains, 0)
                result[code] = {
                    'info': chains,
                    'code': code,
                    'id': currencyId,
                    'name': self.safe_string(firstChain, 'name'),
                    'active': currencyActive,
                    'deposit': depositEnabled,
                    'withdraw': withdrawEnabled,
                    'fee': None,
                    'precision': self.parse_number(maxPrecision),
                    'limits': {
                        'amount': {
                            'min': None,
                            'max': None,
                        },
                    },
                    'networks': networks,
                }
            return result

    async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-market-data-get-order-book
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int [limit]: the maximum amount of order book entries to return
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'instId': market['id'],
        }
        limit = 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: dict, market: Market = None) -> Ticker:
        #
        #     {
        #         "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_string(ticker, 'last')
        open = self.safe_string(ticker, 'open24h')
        spot = self.safe_bool(market, 'spot', False)
        quoteVolume = self.safe_string(ticker, 'volCcy24h') if spot else None
        baseVolume = self.safe_string(ticker, 'vol24h')
        high = self.safe_string(ticker, 'high24h')
        low = self.safe_string(ticker, 'low24h')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': high,
            'low': low,
            'bid': self.safe_string(ticker, 'bidPx'),
            'bidVolume': self.safe_string(ticker, 'bidSz'),
            'ask': self.safe_string(ticker, 'askPx'),
            'askVolume': self.safe_string(ticker, 'askSz'),
            'vwap': None,
            'open': open,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }, market)

    async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-market-data-get-ticker
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'instId': market['id'],
        }
        response = await self.publicGetMarketTicker(self.extend(request, params))
        data = self.safe_value(response, 'data', [])
        first = self.safe_value(data, 0, {})
        #
        #     {
        #         "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"
        #             }
        #         ]
        #     }
        #
        return self.parse_ticker(first, market)

    async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-market-data-get-tickers
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
        :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        symbols = self.market_symbols(symbols)
        request: dict = {
            'instType': 'SPOT',
        }
        response = await self.publicGetMarketTickers(self.extend(request, params))
        data = self.safe_list(response, 'data', [])
        return self.parse_tickers(data, symbols, params)

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        # 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: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-market-data-get-trades
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-market-data-get-trades-history
        get the list of most recent trades for a particular symbol
        :param str symbol: unified symbol of the market to fetch trades for
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
        """
        await self.load_markets()
        market = self.market(symbol)
        if (limit is None) or (limit > 100):
            limit = 100  # maximum = default = 100
        request: dict = {
            'instId': market['id'],
        }
        method = None
        method, params = self.handle_option_and_params(params, 'fetchTrades', 'method', 'publicGetMarketTrades')
        response = None
        if method == 'publicGetMarketTrades':
            response = await self.publicGetMarketTrades(self.extend(request, params))
        else:
            response = await self.publicGetMarketHistoryTrades(self.extend(request, params))
        data = self.safe_list(response, 'data', [])
        return self.parse_trades(data, market, since, limit)

    def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
        #
        #     [
        #         "1678928760000",  # timestamp
        #         "24341.4",  # open
        #         "24344",  # high
        #         "24313.2",  # low
        #         "24323",  # close
        #         "628",  # contract volume
        #         "2.5819",  # base volume
        #         "62800",  # quote volume
        #         "0"  # candlestick state
        #     ]
        #
        return [
            self.safe_integer(ohlcv, 0),
            self.safe_number(ohlcv, 1),
            self.safe_number(ohlcv, 2),
            self.safe_number(ohlcv, 3),
            self.safe_number(ohlcv, 4),
            self.safe_number(ohlcv, 5),
        ]

    async def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-market-data-get-candlesticks
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-market-data-get-candlesticks-history
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int [since]: timestamp in ms of the earliest candle to fetch
        :param int [limit]: the maximum amount of candles to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        await self.load_markets()
        market = self.market(symbol)
        duration = self.parse_timeframe(timeframe)
        options = self.safe_value(self.options, 'fetchOHLCV', {})
        bar = self.safe_string(self.timeframes, timeframe, timeframe)
        timezone = self.safe_string(options, 'timezone', 'UTC')
        if (timezone == 'UTC') and (duration >= 21600):  # if utc and timeframe >= 6h
            bar += timezone.lower()
        request: dict = {
            'instId': market['id'],
            'bar': bar,
        }
        if limit is not None:
            request['limit'] = limit  # default 100, max 100
        method = None
        method, params = self.handle_option_and_params(params, 'fetchOHLCV', 'method', 'publicGetMarketCandles')
        response = None
        if method == 'publicGetMarketCandles':
            response = await self.publicGetMarketCandles(self.extend(request, params))
        else:
            response = await self.publicGetMarketHistoryCandles(self.extend(request, params))
        data = self.safe_list(response, 'data', [])
        return self.parse_ohlcvs(data, market, timeframe, since, limit)

    def parse_account_balance(self, response):
        #
        # account
        #
        #     [
        #         {
        #             "balance":  0,
        #             "available":  0,
        #             "currency": "BTC",
        #             "hold":  0
        #         },
        #         {
        #             "balance":  0,
        #             "available":  0,
        #             "currency": "ETH",
        #             "hold":  0
        #         }
        #     ]
        #
        # spot
        #
        #     [
        #         {
        #             "frozen": "0",
        #             "hold": "0",
        #             "id": "2149632",
        #             "currency": "BTC",
        #             "balance": "0.0000000497717339",
        #             "available": "0.0000000497717339",
        #             "holds": "0"
        #         },
        #         {
        #             "frozen": "0",
        #             "hold": "0",
        #             "id": "2149632",
        #             "currency": "ICN",
        #             "balance": "0.00000000925",
        #             "available": "0.00000000925",
        #             "holds": "0"
        #         }
        #     ]
        #
        result: dict = {
            'info': response,
            'timestamp': None,
            'datetime': None,
        }
        for i in range(0, len(response)):
            balance = response[i]
            currencyId = self.safe_string(balance, 'currency')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['total'] = self.safe_string(balance, 'balance')
            account['used'] = self.safe_string(balance, 'hold')
            account['free'] = self.safe_string(balance, 'available')
            result[code] = account
        return self.safe_balance(result)

    async def fetch_balance(self, params={}) -> Balances:
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
        """
        await self.load_markets()
        marketType, query = self.handle_market_type_and_params('fetchBalance', None, params)
        request: dict = {
            # 'ccy': 'BTC,ETH',  # comma-separated list of currency ids
        }
        response = None
        if marketType == 'funding':
            response = await self.privateGetAssetBalances(self.extend(request, query))
        else:
            response = await self.privateGetAccountBalance(self.extend(request, query))
        #
        #  {
        #         "code": "0",
        #         "data": [
        #             {
        #                 "category": "1",
        #                 "delivery": "",
        #                 "exercise": "",
        #                 "instType": "SPOT",
        #                 "level": "Lv1",
        #                 "maker": "-0.0008",
        #                 "taker": "-0.001",
        #                 "ts": "1639043138472"
        #             }
        #         ],
        #         "msg": ""
        #     }
        #
        if marketType == 'funding':
            return self.parse_funding_balance(response)
        else:
            return self.parse_trading_balance(response)

    def parse_trading_balance(self, response):
        result: dict = {'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 (eq is None) or (availEq is None):
                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: dict = {'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)

    async def create_market_buy_order_with_cost(self, symbol: str, cost: float, params={}):
        """
        create a market buy order by providing the symbol and cost
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-place-order
        :param str symbol: unified symbol of the market to create an order in
        :param float cost: how much you want to trade in units of the quote currency
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        if not market['spot']:
            raise NotSupported(self.id + ' createMarketBuyOrderWithCost() supports spot orders only')
        params['createMarketBuyOrderRequiresPrice'] = False
        params['tgtCcy'] = 'quote_ccy'
        return await self.create_order(symbol, 'market', 'buy', cost, None, params)

    async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-place-order
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-place-algo-order
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-place-multiple-orders
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-cancel-advance-algo-order
        create a trade order
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float price: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param bool [params.reduceOnly]: MARGIN orders only, or swap/future orders in net mode
        :param bool [params.postOnly]: True to place a post only order
        :param float [params.triggerPrice]: conditional orders only, the price at which the order is to be triggered
        :param dict [params.takeProfit]: *takeProfit object in params* containing the triggerPrice at which the attached take profit order will be triggered(perpetual swap markets only)
        :param float [params.takeProfit.triggerPrice]: take profit trigger price
        :param float [params.takeProfit.price]: used for take profit limit orders, not used for take profit market price orders
        :param str [params.takeProfit.type]: 'market' or 'limit' used to specify the take profit price type
        :param dict [params.stopLoss]: *stopLoss object in params* containing the triggerPrice at which the attached stop loss order will be triggered(perpetual swap markets only)
        :param float [params.stopLoss.triggerPrice]: stop loss trigger price
        :param float [params.stopLoss.price]: used for stop loss limit orders, not used for stop loss market price orders
        :param str [params.stopLoss.type]: 'market' or 'limit' used to specify the stop loss price type
        :param float [params.cost]: *spot market buy only* the quote quantity that can be used alternative for the amount
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request = self.create_order_request(symbol, type, side, amount, price, params)
        method = self.safe_string(self.options, 'createOrder', 'privatePostTradeBatchOrders')
        requestOrdType = self.safe_string(request, 'ordType')
        if (requestOrdType == 'trigger') or (requestOrdType == 'conditional') or (type == 'oco') or (type == 'move_order_stop') or (type == 'iceberg') or (type == 'twap'):
            method = 'privatePostTradeOrderAlgo'
        if method == '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
            request = [request]
        response = None
        if method == 'privatePostTradeOrder':
            response = await self.privatePostTradeOrder(request)
        elif method == 'privatePostTradeOrderAlgo':
            response = await self.privatePostTradeOrderAlgo(request)
        elif method == 'privatePostTradeBatchOrders':
            response = await self.privatePostTradeBatchOrders(request)
        else:
            raise ExchangeError(self.id + ' createOrder() self.options["createOrder"] must be either privatePostTradeBatchOrders or privatePostTradeOrder or privatePostTradeOrderAlgo')
        data = self.safe_value(response, 'data', [])
        first = self.safe_value(data, 0)
        order = self.parse_order(first, market)
        order['type'] = type
        order['side'] = side
        return order

    def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        market = self.market(symbol)
        request: dict = {
            'instId': market['id'],
            # '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
            'side': side,
            # 'posSide': 'long',  # long, short,  # required in the long/short mode, and can only be long or short(only for future or swap)
            'ordType': type,
            # 'ordType': type,  # privatePostTradeOrder: market, limit, post_only, fok, ioc, optimal_limit_ioc
            # 'ordType': type,  # privatePostTradeOrderAlgo: conditional, oco, trigger, move_order_stop, iceberg, twap
            # 'sz': self.amount_to_precision(symbol, amount),
            # 'px': self.price_to_precision(symbol, price),  # limit orders only
            # 'reduceOnly': False,
            #
            # 'triggerPx': 10,  # stopPrice(trigger orders)
            # 'orderPx': 10,  # Order price if -1, the order will be executed at the market price.(trigger orders)
            # 'triggerPxType': 'last',  # Conditional default is last, mark or index(trigger orders)
            #
            # 'tpTriggerPx': 10,  # takeProfitPrice(conditional orders)
            # 'tpTriggerPxType': 'last',  # Conditional default is last, mark or index(conditional orders)
            # 'tpOrdPx': 10,  # Order price for Take-Profit orders, if -1 will be executed at market price(conditional orders)
            #
            # 'slTriggerPx': 10,  # stopLossPrice(conditional orders)
            # 'slTriggerPxType': 'last',  # Conditional default is last, mark or index(conditional orders)
            # 'slOrdPx': 10,  # Order price for Stop-Loss orders, if -1 will be executed at market price(conditional orders)
        }
        triggerPrice = self.safe_value_n(params, ['triggerPrice', 'stopPrice', 'triggerPx'])
        timeInForce = self.safe_string(params, 'timeInForce', 'GTC')
        takeProfitPrice = self.safe_value_2(params, 'takeProfitPrice', 'tpTriggerPx')
        tpOrdPx = self.safe_value(params, 'tpOrdPx', price)
        tpTriggerPxType = self.safe_string(params, 'tpTriggerPxType', 'last')
        stopLossPrice = self.safe_value_2(params, 'stopLossPrice', 'slTriggerPx')
        slOrdPx = self.safe_value(params, 'slOrdPx', price)
        slTriggerPxType = self.safe_string(params, 'slTriggerPxType', 'last')
        clientOrderId = self.safe_string_2(params, 'clOrdId', 'clientOrderId')
        stopLoss = self.safe_value(params, 'stopLoss')
        stopLossDefined = (stopLoss is not None)
        takeProfit = self.safe_value(params, 'takeProfit')
        takeProfitDefined = (takeProfit is not None)
        defaultMarginMode = self.safe_string_2(self.options, 'defaultMarginMode', 'marginMode', 'cross')
        marginMode = self.safe_string_2(params, 'marginMode', 'tdMode')  # cross or isolated, tdMode not ommited so be extended into the request
        margin = False
        if (marginMode is not None) and (marginMode != 'cash'):
            margin = True
        else:
            marginMode = defaultMarginMode
            margin = self.safe_bool(params, 'margin', False)
        if margin:
            defaultCurrency = market['quote'] if (side == 'buy') else market['base']
            currency = self.safe_string(params, 'ccy', defaultCurrency)
            request['ccy'] = self.safe_currency_code(currency)
        tradeMode = marginMode if margin else 'cash'
        request['tdMode'] = tradeMode
        isMarketOrder = type == 'market'
        postOnly = False
        postOnly, params = self.handle_post_only(isMarketOrder, type == 'post_only', params)
        params = self.omit(params, ['currency', 'ccy', 'marginMode', 'timeInForce', 'stopPrice', 'triggerPrice', 'clientOrderId', 'stopLossPrice', 'takeProfitPrice', 'slOrdPx', 'tpOrdPx', 'margin', 'stopLoss', 'takeProfit'])
        ioc = (timeInForce == 'IOC') or (type == 'ioc')
        fok = (timeInForce == 'FOK') or (type == 'fok')
        trigger = (triggerPrice is not None) or (type == 'trigger')
        conditional = (stopLossPrice is not None) or (takeProfitPrice is not None) or (type == 'conditional')
        marketIOC = (isMarketOrder and ioc) or (type == 'optimal_limit_ioc')
        defaultTgtCcy = self.safe_string(self.options, 'tgtCcy', 'base_ccy')
        tgtCcy = self.safe_string(params, 'tgtCcy', defaultTgtCcy)
        if (not margin):
            request['tgtCcy'] = tgtCcy
        if isMarketOrder or marketIOC:
            request['ordType'] = 'market'
            if side == 'buy':
                # spot market buy: "sz" can refer either to base currency units or to quote currency units
                # see documentation: https://www.okx.com/docs-v5/en/#rest-api-trade-place-order
                if tgtCcy == 'quote_ccy':
                    # quote_ccy: sz refers to units of quote currency
                    quoteAmount = None
                    createMarketBuyOrderRequiresPrice = True
                    createMarketBuyOrderRequiresPrice, params = self.handle_option_and_params(params, 'createOrder', 'createMarketBuyOrderRequiresPrice', True)
                    cost = self.safe_number_2(params, 'cost', 'sz')
                    params = self.omit(params, ['cost', 'sz'])
                    if cost is not None:
                        quoteAmount = self.cost_to_precision(symbol, cost)
                    elif createMarketBuyOrderRequiresPrice:
                        if price is None:
                            raise InvalidOrder(self.id + ' createOrder() requires the price argument for market buy orders to calculate the total cost to spend(amount * price), alternatively set the createMarketBuyOrderRequiresPrice option or param to False and pass the cost to spend(quote quantity) in the amount argument')
                        else:
                            amountString = self.number_to_string(amount)
                            priceString = self.number_to_string(price)
                            costRequest = Precise.string_mul(amountString, priceString)
                            quoteAmount = self.cost_to_precision(symbol, costRequest)
                    else:
                        quoteAmount = self.cost_to_precision(symbol, amount)
                    request['sz'] = quoteAmount
                else:
                    request['sz'] = self.amount_to_precision(symbol, amount)
            else:
                request['sz'] = self.amount_to_precision(symbol, amount)
        else:
            request['sz'] = self.amount_to_precision(symbol, amount)
            if (not trigger) and (not conditional):
                request['px'] = self.price_to_precision(symbol, price)
        if postOnly:
            request['ordType'] = 'post_only'
        elif ioc and not marketIOC:
            request['ordType'] = 'ioc'
        elif fok:
            request['ordType'] = 'fok'
        elif stopLossDefined or takeProfitDefined:
            if stopLossDefined:
                stopLossTriggerPrice = self.safe_value_n(stopLoss, ['triggerPrice', 'stopPrice', 'slTriggerPx'])
                if stopLossTriggerPrice is None:
                    raise InvalidOrder(self.id + ' createOrder() requires a trigger price in params["stopLoss"]["triggerPrice"], or params["stopLoss"]["stopPrice"], or params["stopLoss"]["slTriggerPx"] for a stop loss order')
                request['slTriggerPx'] = self.price_to_precision(symbol, stopLossTriggerPrice)
                stopLossLimitPrice = self.safe_value_n(stopLoss, ['price', 'stopLossPrice', 'slOrdPx'])
                stopLossOrderType = self.safe_string(stopLoss, 'type')
                if stopLossOrderType is not None:
                    stopLossLimitOrderType = (stopLossOrderType == 'limit')
                    stopLossMarketOrderType = (stopLossOrderType == 'market')
                    if (not stopLossLimitOrderType) and (not stopLossMarketOrderType):
                        raise InvalidOrder(self.id + ' createOrder() params["stopLoss"]["type"] must be either "limit" or "market"')
                    elif stopLossLimitOrderType:
                        if stopLossLimitPrice is None:
                            raise InvalidOrder(self.id + ' createOrder() requires a limit price in params["stopLoss"]["price"] or params["stopLoss"]["slOrdPx"] for a stop loss limit order')
                        else:
                            request['slOrdPx'] = self.price_to_precision(symbol, stopLossLimitPrice)
                    elif stopLossOrderType == 'market':
                        request['slOrdPx'] = '-1'
                elif stopLossLimitPrice is not None:
                    request['slOrdPx'] = self.price_to_precision(symbol, stopLossLimitPrice)  # limit sl order
                else:
                    request['slOrdPx'] = '-1'  # market sl order
                stopLossTriggerPriceType = self.safe_string_2(stopLoss, 'triggerPriceType', 'slTriggerPxType', 'last')
                if stopLossTriggerPriceType is not None:
                    if (stopLossTriggerPriceType != 'last') and (stopLossTriggerPriceType != 'index') and (stopLossTriggerPriceType != 'mark'):
                        raise InvalidOrder(self.id + ' createOrder() stop loss trigger price type must be one of "last", "index" or "mark"')
                    request['slTriggerPxType'] = stopLossTriggerPriceType
            if takeProfitDefined:
                takeProfitTriggerPrice = self.safe_value_n(takeProfit, ['triggerPrice', 'stopPrice', 'tpTriggerPx'])
                if takeProfitTriggerPrice is None:
                    raise InvalidOrder(self.id + ' createOrder() requires a trigger price in params["takeProfit"]["triggerPrice"], or params["takeProfit"]["stopPrice"], or params["takeProfit"]["tpTriggerPx"] for a take profit order')
                request['tpTriggerPx'] = self.price_to_precision(symbol, takeProfitTriggerPrice)
                takeProfitLimitPrice = self.safe_value_n(takeProfit, ['price', 'takeProfitPrice', 'tpOrdPx'])
                takeProfitOrderType = self.safe_string(takeProfit, 'type')
                if takeProfitOrderType is not None:
                    takeProfitLimitOrderType = (takeProfitOrderType == 'limit')
                    takeProfitMarketOrderType = (takeProfitOrderType == 'market')
                    if (not takeProfitLimitOrderType) and (not takeProfitMarketOrderType):
                        raise InvalidOrder(self.id + ' createOrder() params["takeProfit"]["type"] must be either "limit" or "market"')
                    elif takeProfitLimitOrderType:
                        if takeProfitLimitPrice is None:
                            raise InvalidOrder(self.id + ' createOrder() requires a limit price in params["takeProfit"]["price"] or params["takeProfit"]["tpOrdPx"] for a take profit limit order')
                        else:
                            request['tpOrdPx'] = self.price_to_precision(symbol, takeProfitLimitPrice)
                    elif takeProfitOrderType == 'market':
                        request['tpOrdPx'] = '-1'
                elif takeProfitLimitPrice is not None:
                    request['tpOrdPx'] = self.price_to_precision(symbol, takeProfitLimitPrice)  # limit tp order
                else:
                    request['tpOrdPx'] = '-1'  # market tp order
                takeProfitTriggerPriceType = self.safe_string_2(takeProfit, 'triggerPriceType', 'tpTriggerPxType', 'last')
                if takeProfitTriggerPriceType is not None:
                    if (takeProfitTriggerPriceType != 'last') and (takeProfitTriggerPriceType != 'index') and (takeProfitTriggerPriceType != 'mark'):
                        raise InvalidOrder(self.id + ' createOrder() take profit trigger price type must be one of "last", "index" or "mark"')
                    request['tpTriggerPxType'] = takeProfitTriggerPriceType
        elif trigger:
            request['ordType'] = 'trigger'
            request['triggerPx'] = self.price_to_precision(symbol, triggerPrice)
            request['orderPx'] = '-1' if isMarketOrder else self.price_to_precision(symbol, price)
        elif conditional:
            request['ordType'] = 'conditional'
            twoWayCondition = ((takeProfitPrice is not None) and (stopLossPrice is not None))
            # if TP and SL are sent together
            # 'conditional' only stop-loss order will be applied
            if twoWayCondition:
                request['ordType'] = 'oco'
            if takeProfitPrice is not None:
                request['tpTriggerPx'] = self.price_to_precision(symbol, takeProfitPrice)
                request['tpOrdPx'] = '-1' if (tpOrdPx is None) else self.price_to_precision(symbol, tpOrdPx)
                request['tpTriggerPxType'] = tpTriggerPxType
            if stopLossPrice is not None:
                request['slTriggerPx'] = self.price_to_precision(symbol, stopLossPrice)
                request['slOrdPx'] = '-1' if (slOrdPx is None) else self.price_to_precision(symbol, slOrdPx)
                request['slTriggerPxType'] = slTriggerPxType
        if clientOrderId is None:
            brokerId = self.safe_string(self.options, 'brokerId')
            if brokerId is not None:
                request['clOrdId'] = brokerId + self.uuid16()
                request['tag'] = brokerId
        else:
            request['clOrdId'] = clientOrderId
            params = self.omit(params, ['clOrdId', 'clientOrderId'])
        return self.extend(request, params)

    async def cancel_order(self, id: str, symbol: Str = None, params={}):
        """
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-cancel-order
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-cancel-algo-order
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-cancel-advance-algo-order
        cancels an open order
        :param str id: order id
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param bool [params.stop]: True if cancel trigger or conditional orders
        :param bool [params.advanced]: True if canceling advanced orders only
        :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        await self.load_markets()
        stop = self.safe_value_2(params, 'stop', 'trigger')
        advanced = self.safe_value(params, 'advanced')
        if stop or advanced:
            orderInner = await self.cancel_orders([id], symbol, params)
            return self.safe_value(orderInner, 0)
        market = self.market(symbol)
        request: dict = {
            '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'] = str(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_dict(data, 0)
        return self.parse_order(order, market)

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

    async def cancel_orders(self, ids, symbol: Str = None, params={}):
        """
        cancel multiple orders
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-cancel-multiple-orders
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-cancel-algo-order
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-cancel-advance-algo-order
        :param str[] ids: order ids
        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrders() requires a symbol argument')
        await self.load_markets()
        stop = self.safe_value_2(params, 'stop', 'trigger')
        advanced = self.safe_value(params, 'advanced')
        params = self.omit(params, ['stop', 'trigger', 'advanced'])
        market = self.market(symbol)
        request = []
        clientOrderIds = self.parse_ids(self.safe_value_2(params, 'clOrdId', 'clientOrderId'))
        algoIds = self.parse_ids(self.safe_value(params, 'algoId'))
        if clientOrderIds is None:
            ids = self.parse_ids(ids)
            if algoIds is not None:
                for i in range(0, len(algoIds)):
                    request.append({
                        'algoId': algoIds[i],
                        'instId': market['id'],
                    })
            for i in range(0, len(ids)):
                if stop or advanced:
                    request.append({
                        'algoId': ids[i],
                        'instId': market['id'],
                    })
                else:
                    request.append({
                        'ordId': ids[i],
                        'instId': market['id'],
                    })
        else:
            for i in range(0, len(clientOrderIds)):
                request.append({
                    'instId': market['id'],
                    'clOrdId': clientOrderIds[i],
                })
        response = None
        if stop:
            response = await self.privatePostTradeCancelAlgos(request)
        elif advanced:
            response = await self.privatePostTradeCancelAdvanceAlgos(request)
        else:
            response = await self.privatePostTradeCancelBatchOrders(request)  # * dont self.extend with params, otherwise ARRAY will be turned into OBJECT
        #
        #     {
        #         "code": "0",
        #         "data": [
        #             {
        #                 "clOrdId": "e123456789ec4dBC1123456ba123b45e",
        #                 "ordId": "405071912345641543",
        #                 "sCode": "0",
        #                 "sMsg": ""
        #             },
        #             ...
        #         ],
        #         "msg": ""
        #     }
        #
        #
        ordersData = self.safe_list(response, 'data', [])
        return self.parse_orders(ordersData, market, None, None, params)

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

    def parse_order(self, order: dict, market: Market = None) -> Order:
        #
        # createOrder
        #
        #     {
        #         "clOrdId": "oktswap6",
        #         "ordId": "312269865356374016",
        #         "tag": "",
        #         "sCode": "0",
        #         "sMsg": ""
        #     }
        #
        # editOrder
        #
        #     {
        #         "clOrdId": "e847386590ce4dBCc1a045253497a547",
        #         "ordId": "559176536793178112",
        #         "reqId": "",
        #         "sCode": "0",
        #         "sMsg": ""
        #     }
        #
        # Spot and Swap 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"
        #     }
        #
        # Algo Order fetchOpenOrders, fetchCanceledOrders, fetchClosedOrders
        #
        #     {
        #         "activePx": "",
        #         "activePxType": "",
        #         "actualPx": "",
        #         "actualSide": "buy",
        #         "actualSz": "0",
        #         "algoId": "431375349042380800",
        #         "cTime": "1649119897778",
        #         "callbackRatio": "",
        #         "callbackSpread": "",
        #         "ccy": "",
        #         "ctVal": "0.01",
        #         "instId": "BTC-USDT-SWAP",
        #         "instType": "SWAP",
        #         "last": "46538.9",
        #         "lever": "125",
        #         "moveTriggerPx": "",
        #         "notionalUsd": "467.059",
        #         "ordId": "",
        #         "ordPx": "50000",
        #         "ordType": "trigger",
        #         "posSide": "long",
        #         "pxLimit": "",
        #         "pxSpread": "",
        #         "pxVar": "",
        #         "side": "buy",
        #         "slOrdPx": "",
        #         "slTriggerPx": "",
        #         "slTriggerPxType": "",
        #         "state": "live",
        #         "sz": "1",
        #         "szLimit": "",
        #         "tag": "",
        #         "tdMode": "isolated",
        #         "tgtCcy": "",
        #         "timeInterval": "",
        #         "tpOrdPx": "",
        #         "tpTriggerPx": "",
        #         "tpTriggerPxType": "",
        #         "triggerPx": "50000",
        #         "triggerPxType": "last",
        #         "triggerTime": "",
        #         "uly": "BTC-USDT"
        #     }
        #
        id = self.safe_string_2(order, 'algoId', 'ordId')
        timestamp = self.safe_integer(order, 'cTime')
        lastUpdateTimestamp = self.safe_integer(order, 'uTime')
        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')
        market = self.safe_market(marketId, market)
        symbol = self.safe_symbol(marketId, market, '-')
        filled = self.safe_string(order, 'accFillSz')
        price = self.safe_string_2(order, 'px', 'ordPx')
        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.okx.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)
        if (side == 'buy') and (type == 'market') 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
        stopLossPrice = self.safe_number_2(order, 'slTriggerPx', 'slOrdPx')
        takeProfitPrice = self.safe_number_2(order, 'tpTriggerPx', 'tpOrdPx')
        stopPrice = self.safe_number_n(order, ['triggerPx', 'moveTriggerPx'])
        reduceOnlyRaw = self.safe_string(order, 'reduceOnly')
        reduceOnly = False
        if reduceOnly is not None:
            reduceOnly = (reduceOnlyRaw == 'true')
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'lastUpdateTimestamp': lastUpdateTimestamp,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': postOnly,
            'side': side,
            'price': price,
            'stopLossPrice': stopLossPrice,
            'takeProfitPrice': takeProfitPrice,
            'stopPrice': stopPrice,
            'triggerPrice': stopPrice,
            'average': average,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'remaining': None,
            'status': status,
            'fee': fee,
            'trades': None,
            'reduceOnly': reduceOnly,
        }, market)

    async def fetch_order(self, id: str, symbol: Str = None, params={}):
        """
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-get-order-details
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-get-algo-order-list
        fetches information on an order made by the user
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'instId': market['id'],
            # 'clOrdId': 'abcdef12345',  # optional, [a-z0-9]{1,32}
            # 'ordId': id,
        }
        clientOrderId = self.safe_string_2(params, 'clOrdId', 'clientOrderId')
        stop = self.safe_value_2(params, 'stop', 'trigger')
        if stop:
            if clientOrderId is not None:
                request['algoClOrdId'] = clientOrderId
            else:
                request['algoId'] = id
        else:
            if clientOrderId is not None:
                request['clOrdId'] = clientOrderId
            else:
                request['ordId'] = id
        query = self.omit(params, ['clientOrderId', 'stop', 'trigger'])
        response = None
        if stop:
            response = await self.privateGetTradeOrderAlgo(self.extend(request, query))
        else:
            response = await self.privateGetTradeOrder(self.extend(request, query))
        data = self.safe_value(response, 'data', [])
        order = self.safe_dict(data, 0)
        return self.parse_order(order)

    async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-get-order-list
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-get-algo-order-list
        fetch all unfilled currently open orders
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch open orders for
        :param int [limit]: the maximum number of  open orders structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param bool [params.stop]: True if fetching trigger or conditional orders
        :param str [params.ordType]: "conditional", "oco", "trigger", "move_order_stop", "iceberg", or "twap"
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        request: dict = {
            # 'instId': market['id'],
            # 'ordType': 'limit',  # market, limit, post_only, fok, ioc, comma-separated, stop orders: conditional, oco, trigger, move_order_stop, iceberg, or twap
            # '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
        ordType = self.safe_string(params, 'ordType')
        stop = self.safe_value(params, 'stop') or (self.safe_string(params, 'ordType') is not None)
        if stop and (ordType is None):
            request['ordType'] = 'trigger'  # default to trigger
        params = self.omit(params, ['stop'])
        response = None
        if stop:
            response = await self.privateGetTradeOrdersAlgoPending(self.extend(request, params))
        else:
            response = await self.privateGetTradeOrdersPending(self.extend(request, params))
        data = self.safe_list(response, 'data', [])
        return self.parse_orders(data, market, since, limit)

    async def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-get-algo-order-history
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-get-order-history-last-3-months
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-get-order-history-last-7-days
        fetches information on multiple closed orders made by the user
        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param bool [params.stop]: True if fetching trigger or conditional orders
        :param str [params.ordType]: "conditional", "oco", "trigger", "move_order_stop", "iceberg", or "twap"
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        request: dict = {
            'instType': 'SPOT',
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['instId'] = market['id']
        ordType = self.safe_string(params, 'ordType')
        stop = self.safe_value(params, 'stop') or (self.safe_string(params, 'ordType') is not None)
        if stop and (ordType is None):
            request['ordType'] = 'trigger'  # default to trigger
        params = self.omit(params, ['stop'])
        response = None
        if stop:
            response = await self.privateGetTradeOrdersAlgoHistory(self.extend(request, params))
        else:
            method = None
            method, params = self.handle_option_and_params(params, 'fetchClosedOrders', 'method', 'privateGetTradeOrdersHistory')
            if method == 'privateGetTradeOrdersHistory':
                response = await self.privateGetTradeOrdersHistory(self.extend(request, params))
            else:
                response = await self.privateGetTradeOrdersHistoryArchive(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":"20 00",
        #                 "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_list(response, 'data', [])
        return self.parse_orders(data, market, since, limit)

    def parse_deposit_address(self, depositAddress, currency: 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",
        #        "addrEx": {"comment": "6040348"},  # some currencies like TON may have self field,
        #        "ctAddr": "72315c",
        #        "ccy": "ETH",
        #        "to": "6",
        #        "addr": "0x1c9f2244d1ccaa060bd536827c18925db10db102",
        #        "selected": True
        #     }
        #
        address = self.safe_string(depositAddress, 'addr')
        tag = self.safe_string_n(depositAddress, ['tag', 'pmtId', 'memo'])
        if tag is None:
            addrEx = self.safe_value(depositAddress, 'addrEx', {})
            tag = self.safe_string(addrEx, 'comment')
        currencyId = self.safe_string(depositAddress, 'ccy')
        currency = self.safe_currency(currencyId, currency)
        code = currency['code']
        chain = self.safe_string(depositAddress, 'chain')
        networkId = chain.replace(currencyId + '-', '')
        network = self.network_id_to_code(networkId)
        # inconsistent naming responses from exchange
        # with respect to network naming provided in currency info vs address chain-names and ids
        #
        # response from address endpoint:
        #      {
        #          "chain": "USDT-Polygon",
        #          "ctAddr": "",
        #          "ccy": "USDT",
        #          "to":"6" ,
        #          "addr": "0x1903441e386cc49d937f6302955b5feb4286dcfa",
        #          "selected": True
        #      }
        # network information from currency['networks'] field:
        # Polygon: {
        #        "info": {
        #            "canDep": False,
        #            "canInternal": False,
        #            "canWd": False,
        #            "ccy": "USDT",
        #            "chain": "USDT-Polygon-Bridge",
        #            "mainNet": False,
        #            "maxFee": "26.879528",
        #            "minFee": "13.439764",
        #            "minWd": "0.001",
        #            "name": ''
        #        },
        #        "id": "USDT-Polygon-Bridge",
        #        "network": "Polygon",
        #        "active": False,
        #        "deposit": False,
        #        "withdraw": False,
        #        "fee": 13.439764,
        #        "precision": None,
        #        "limits": {
        #            "withdraw": {
        #                "min": 0.001,
        #                "max": None
        #            }
        #        }
        #     },
        #
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'network': network,
            'info': depositAddress,
        }

    async def fetch_deposit_address(self, code: str, params={}):
        """
        fetch the deposit address for a currency associated with self account
        :see: https://www.okx.com/docs-v5/en/#funding-account-rest-api-get-deposit-address
        :param str code: unified currency code
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
        """
        await self.load_markets()
        defaultNetwork = self.safe_string(self.options, 'defaultNetwork', 'ERC20')
        networkId = self.safe_string(params, 'network', defaultNetwork)
        networkCode = self.network_id_to_code(networkId)
        params = self.omit(params, 'network')
        response = await self.fetch_deposit_addresses_by_network(code, params)
        result = self.safe_value(response, networkCode)
        if result is None:
            raise InvalidAddress(self.id + ' fetchDepositAddress() cannot find ' + networkCode + ' deposit address for ' + code)
        return result

    async def fetch_deposit_addresses_by_network(self, code: str, params={}):
        """
        fetch a dictionary of addresses for a currency, indexed by network
        :see: https://www.okx.com/docs-v5/en/#funding-account-rest-api-get-deposit-address
        :param str code: unified currency code of the currency for the deposit address
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `address structures <https://docs.ccxt.com/#/?id=address-structure>` indexed by the network
        """
        await self.load_markets()
        currency = self.currency(code)
        request: dict = {
            '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, [currency['code']], False)
        return self.index_by(parsed, 'network')

    async def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
        """
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-funding-funds-transfer
        transfer currency internally between wallets on the same account
        :param str code: unified currency code
        :param float amount: amount to transfer
        :param str fromAccount: account to transfer from
        :param str toAccount: account to transfer to
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
        """
        await self.load_markets()
        currency = self.currency(code)
        accountsByType = self.safe_value(self.options, 'accountsByType', {})
        fromId = self.safe_string(accountsByType, fromAccount, fromAccount)
        toId = self.safe_string(accountsByType, toAccount, toAccount)
        request: dict = {
            '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, 3 = sub-account to master account(Only applicable to APIKey from sub-account), 4 = sub-account to sub-account
            'from': fromId,  # remitting account, 6: Funding account, 18: Trading account
            'to': toId,  # beneficiary account, 6: Funding account, 18: Trading account
            # 'subAcct': 'sub-account-name',  # optional, only required when type is 1, 2 or 4
            # 'loanTrans': False,  # Whether or not borrowed coins can be transferred out under Multi-currency margin and Portfolio margin. The default is False
            # 'clientId': 'client-supplied id',  # A combination of case-sensitive alphanumerics, all numbers, or all letters of up to 32 characters
            # 'omitPosRisk': False,  # Ignore position risk. Default is False. Applicable to Portfolio margin
        }
        if fromId == 'master':
            request['type'] = '1'
            request['subAcct'] = toId
            request['from'] = self.safe_string(params, 'from', '6')
            request['to'] = self.safe_string(params, 'to', '6')
        elif toId == 'master':
            request['type'] = '2'
            request['subAcct'] = fromId
            request['from'] = self.safe_string(params, 'from', '6')
            request['to'] = self.safe_string(params, 'to', '6')
        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_dict(data, 0, {})
        return self.parse_transfer(rawTransfer, currency)

    def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
        #
        # transfer
        #
        #     {
        #         "transId": "754147",
        #         "ccy": "USDT",
        #         "from": "6",
        #         "amt": "0.1",
        #         "to": "18"
        #     }
        #
        # fetchTransfer
        #
        #     {
        #         "amt": "5",
        #         "ccy": "USDT",
        #         "from": "18",
        #         "instId": "",
        #         "state": "success",
        #         "subAcct": "",
        #         "to": "6",
        #         "toInstId": "",
        #         "transId": "464424732",
        #         "type": "0"
        #     }
        #
        # fetchTransfers
        #
        #     {
        #         "bal": "70.6874353780312913",
        #         "balChg": "-4.0000000000000000",  # negative means "to funding", positive meand "from funding"
        #         "billId": "588900695232225299",
        #         "ccy": "USDT",
        #         "execType": "",
        #         "fee": "",
        #         "from": "18",
        #         "instId": "",
        #         "instType": "",
        #         "mgnMode": "",
        #         "notes": "To Funding Account",
        #         "ordId": "",
        #         "pnl": "",
        #         "posBal": "",
        #         "posBalChg": "",
        #         "price": "0",
        #         "subType": "12",
        #         "sz": "-4",
        #         "to": "6",
        #         "ts": "1686676866989",
        #         "type": "1"
        #     }
        #
        id = self.safe_string_2(transfer, 'transId', 'billId')
        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')
        accountsById = self.safe_value(self.options, 'accountsById', {})
        timestamp = self.safe_integer(transfer, 'ts', self.milliseconds())
        balanceChange = self.safe_string(transfer, 'sz')
        if balanceChange is not None:
            amount = self.parse_number(Precise.string_abs(balanceChange))
        return {
            'info': transfer,
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'currency': code,
            'amount': amount,
            'fromAccount': self.safe_string(accountsById, fromAccountId),
            'toAccount': self.safe_string(accountsById, toAccountId),
            'status': self.parse_transfer_status(self.safe_string(transfer, 'state')),
        }

    def parse_transfer_status(self, status: Str) -> Str:
        statuses: dict = {
            'success': 'ok',
        }
        return self.safe_string(statuses, status, status)

    async def withdraw(self, code: str, amount: float, address: str, tag=None, params={}):
        """
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-funding-withdrawal
        make a withdrawal
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str tag:
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        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) and (len(tag) > 0):
            address = address + ':' + tag
        request: dict = {
            'ccy': currency['id'],
            'toAddr': address,
            'dest': '4',
            'amt': self.number_to_string(amount),
        }
        network = self.safe_string(params, 'network')  # self line allows the user to specify either ERC20 or ETH
        if network is not None:
            networks = self.safe_value(self.options, 'networks', {})
            network = self.safe_string(networks, network.upper(), network)  # handle ETH>ERC20 alias
            request['chain'] = currency['id'] + '-' + network
            params = self.omit(params, 'network')
        fee = self.safe_string(params, 'fee')
        if fee is None:
            targetNetwork = self.safe_value(currency['networks'], self.network_id_to_code(network), {})
            fee = self.safe_string(targetNetwork, '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 OKX are fee-free, please set "0". Withdrawing to external digital asset address requires network transaction fee.')
        request['fee'] = self.number_to_string(fee)  # withdrawals to OKCoin or OKX are fee-free, please set 0
        response = await self.privatePostAssetWithdrawal(self.extend(request, params))
        #
        #     {
        #         "code": "0",
        #         "msg": "",
        #         "data": [
        #             {
        #                 "amt": "0.1",
        #                 "wdId": "67485",
        #                 "ccy": "BTC"
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        transaction = self.safe_dict(data, 0)
        return self.parse_transaction(transaction, currency)

    async def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-funding-get-deposit-history
        fetch all deposits made to an account
        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch deposits for
        :param int [limit]: the maximum number of deposits structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        request: dict = {
            # '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
        request, params = self.handle_until_option('after', request, params)
        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_list(response, 'data', [])
        return self.parse_transactions(data, currency, since, limit, params)

    async def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-funding-get-withdrawal-history
        fetch all withdrawals made from an account
        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch withdrawals for
        :param int [limit]: the maximum number of withdrawals structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        request: dict = {
            # '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
        request, params = self.handle_until_option('after', request, params)
        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_list(response, 'data', [])
        return self.parse_transactions(data, currency, since, limit, params)

    def parse_transaction_status(self, status: Str):
        #
        # deposit statuses
        #
        #     {
        #         "0": "waiting for confirmation",
        #         "1": "confirmation account",
        #         "2": "recharge success"
        #     }
        #
        # withdrawal statues
        #
        #     {
        #        '-3': "pending cancel",
        #        "-2": "cancelled",
        #        "-1": "failed",
        #         "0": "pending",
        #         "1": "sending",
        #         "2": "sent",
        #         "3": "email confirmation",
        #         "4": "manual confirmation",
        #         "5": "awaiting identity confirmation"
        #     }
        #
        statuses: dict = {
            '-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: dict, currency: Currency = None) -> Transaction:
        #
        # 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",
        #         "pmtId",
        #         "memo",
        #         "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),
            'comment': None,
            'internal': None,
            'fee': {
                'currency': code,
                'cost': feeCost,
            },
        }

    async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-get-transaction-details-last-3-days
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-trade-get-transaction-details-last-3-months
        fetch all trades made by the user
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: the maximum number of trades structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
        """
        await self.load_markets()
        request: dict = {
            'instType': 'SPOT',
        }
        if (limit is not None) and (limit > 100):
            limit = 100
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['instId'] = market['id']
        method = None
        method, params = self.handle_option_and_params(params, 'fetchMyTrades', 'method', 'privateGetTradeFillsHistory')
        response = None
        if method == 'privateGetTradeFillsHistory':
            response = await self.privateGetTradeFillsHistory(self.extend(request, params))
        else:
            response = await self.privateGetTradeFills(self.extend(request, params))
        data = self.safe_list(response, 'data', [])
        return self.parse_trades(data, market, since, limit)

    async def fetch_order_trades(self, id: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all the trades made from a single order
        :param str id: order id
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: the maximum number of trades to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
        """
        request: dict = {
            # 'instrument_id': market['id'],
            'order_id': 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: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-funding-asset-bills-details
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-account-get-bills-details-last-7-days
        :see: https://www.okcoin.com/docs-v5/en/#rest-api-account-get-bills-details-last-3-months
        fetch the history of changes, actions done by the user or operations that altered balance of the user
        :param str code: unified currency code, default is None
        :param int [since]: timestamp in ms of the earliest ledger entry, default is None
        :param int [limit]: max number of ledger entrys to return, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger-structure>`
        """
        await self.load_markets()
        method = None
        method, params = self.handle_option_and_params(params, 'fetchLedger', 'method', 'privateGetAccountBills')
        request: dict = {
            # 'instType': None,  # 'SPOT', 'MARGIN', 'SWAP', 'FUTURES", 'OPTION'
            # 'ccy': None,  # currency['id'],
            # 'ctType': None,  # 'linear', 'inverse', only applicable to FUTURES/SWAP
            # 'type': varies depending the 'method' endpoint :
            #     - https://www.okx.com/docs-v5/en/#rest-api-account-get-bills-details-last-7-days
            #     - https://www.okx.com/docs-v5/en/#rest-api-funding-asset-bills-details
            #     - https://www.okx.com/docs-v5/en/#rest-api-account-get-bills-details-last-3-months
            # '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']
        request, params = self.handle_until_option('end', request, params)
        response = None
        if method == 'privateGetAccountBillsArchive':
            response = await self.privateGetAccountBillsArchive(self.extend(request, params))
        elif method == 'privateGetAssetBills':
            response = await self.privateGetAssetBills(self.extend(request, params))
        else:
            response = await self.privateGetAccountBills(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: dict = {
            '1': 'transfer',  # transfer
            '2': 'trade',  # trade
            '3': 'trade',  # delivery
            '4': 'rebate',  # auto token conversion
            '5': 'trade',  # liquidation
            '6': 'transfer',  # margin transfer
            '7': 'trade',  # interest deduction
            '8': 'fee',  # funding rate
            '9': 'trade',  # adl
            '10': 'trade',  # clawback
            '11': 'trade',  # system token conversion
        }
        return self.safe_string(types, type, type)

    def parse_ledger_entry(self, item: dict, currency: Currency = None):
        #
        # 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 = self.safe_symbol(marketId, None, '-')
        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 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
        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_balance_by_type(self, type, response):
        if type == 'funding':
            return self.parse_funding_balance(response)
        else:
            return self.parse_trading_balance(response)

    def handle_errors(self, httpCode: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
        if not response:
            return None  # 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
        return None
