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

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

from ccxt_zagrava.async_support.base.exchange import Exchange
from ccxt_zagrava.abstract.okcoin import ImplicitAPI
import hashlib
from ccxt_zagrava.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_zagrava.base.errors import ExchangeError
from ccxt_zagrava.base.errors import AuthenticationError
from ccxt_zagrava.base.errors import PermissionDenied
from ccxt_zagrava.base.errors import AccountNotEnabled
from ccxt_zagrava.base.errors import AccountSuspended
from ccxt_zagrava.base.errors import ArgumentsRequired
from ccxt_zagrava.base.errors import BadRequest
from ccxt_zagrava.base.errors import BadSymbol
from ccxt_zagrava.base.errors import InsufficientFunds
from ccxt_zagrava.base.errors import InvalidAddress
from ccxt_zagrava.base.errors import InvalidOrder
from ccxt_zagrava.base.errors import OrderNotFound
from ccxt_zagrava.base.errors import NotSupported
from ccxt_zagrava.base.errors import NetworkError
from ccxt_zagrava.base.errors import RateLimitExceeded
from ccxt_zagrava.base.errors import ExchangeNotAvailable
from ccxt_zagrava.base.errors import OnMaintenance
from ccxt_zagrava.base.errors import InvalidNonce
from ccxt_zagrava.base.errors import RequestTimeout
from ccxt_zagrava.base.errors import CancelPending
from ccxt_zagrava.base.decimal_to_precision import TICK_SIZE
from ccxt_zagrava.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_zagrava/ccxt_zagrava/issues/4981
                'BOX': 'DefiBox',
                'HOT': 'Hydro Protocol',
                'HSR': 'HC',
                'MAG': 'Maggie',
                'SBTC': 'Super Bitcoin',
                'TRADE': 'Unitrade',
                'YOYO': 'YOYOW',
                'WIN': 'WinToken',  # https://github.com/ccxt_zagrava/ccxt_zagrava/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_zagrava.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_zagrava.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_zagrava.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_zagrava.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_zagrava.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_zagrava.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 fulfilled, 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_zagrava.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_zagrava.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_zagrava.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_zagrava.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_zagrava.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_zagrava.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_zagrava.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_zagrava.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_zagrava.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_zagrava.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_zagrava.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_zagrava.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_zagrava.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_zagrava.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_zagrava.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
