text: # -*- coding: utf-8 -*-
  
  # PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
  # https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
  
  from ccxt.base.exchange import Exchange
  
  # -----------------------------------------------------------------------------
  
  try:
      basestring  # Python 3
  except NameError:
      basestring = str  # Python 2
  import hashlib
  from ccxt.base.errors import ExchangeError
  from ccxt.base.errors import AuthenticationError
  from ccxt.base.errors import PermissionDenied
  from ccxt.base.errors import AccountSuspended
  from ccxt.base.errors import ArgumentsRequired
  from ccxt.base.errors import BadRequest
  from ccxt.base.errors import BadSymbol
  from ccxt.base.errors import InsufficientFunds
  from ccxt.base.errors import InvalidAddress
  from ccxt.base.errors import InvalidOrder
  from ccxt.base.errors import OrderNotFound
  from ccxt.base.errors import CancelPending
  from ccxt.base.errors import NotSupported
  from ccxt.base.errors import NetworkError
  from ccxt.base.errors import RateLimitExceeded
  from ccxt.base.errors import ExchangeNotAvailable
  from ccxt.base.errors import OnMaintenance
  from ccxt.base.errors import InvalidNonce
  from ccxt.base.errors import RequestTimeout
  from ccxt.base.decimal_to_precision import TRUNCATE
  from ccxt.base.decimal_to_precision import TICK_SIZE
  from ccxt.base.precise import Precise
  
  from ccxt.config import BROKER_ID
  
  class okex(Exchange):
  
      def describe(self):
          return self.deep_extend(super(okex, self).describe(), {
              'id': 'okex',
              'name': 'OKEX',
              'countries': ['CN', 'US'],
              'version': 'v5',
              'rateLimit': 100,
              'pro': True,
              'certified': True,
              'has': {
                  'CORS': None,
                  'spot': True,
                  'margin': True,
                  'swap': True,
                  'future': True,
                  'option': None,
                  'addMargin': True,
                  'cancelAllOrders': None,
                  'cancelOrder': True,
                  'cancelOrders': True,
                  'createDepositAddress': None,
                  'createOrder': True,
                  'createReduceOnlyOrder': None,
                  'deposit': None,
                  'fetchAccounts': None,
                  'fetchAllTradingFees': None,
                  'fetchBalance': True,
                  'fetchBidsAsks': None,
                  'fetchBorrowRate': True,
                  'fetchBorrowRateHistories': None,
                  'fetchBorrowRateHistory': None,
                  'fetchBorrowRates': True,
                  'fetchBorrowRatesPerSymbol': False,
                  'fetchCanceledOrders': None,
                  'fetchClosedOrder': None,
                  'fetchClosedOrders': True,
                  'fetchCurrencies': True,
                  'fetchDeposit': None,
                  'fetchDepositAddress': True,
                  'fetchDepositAddresses': None,
                  'fetchDepositAddressesByNetwork': True,
                  'fetchDeposits': True,
                  'fetchFundingFee': None,
                  'fetchFundingFees': None,
                  'fetchFundingHistory': True,
                  'fetchFundingRate': True,
                  'fetchFundingRateHistory': True,
                  'fetchFundingRates': False,
                  'fetchIndexOHLCV': True,
                  'fetchIsolatedPositions': None,
                  'fetchL3OrderBook': None,
                  'fetchLedger': True,
                  'fetchLedgerEntry': None,
                  'fetchLeverage': True,
                  'fetchMarkets': True,
                  'fetchMarkOHLCV': True,
                  'fetchMyBuys': None,
                  'fetchMySells': None,
                  'fetchMyTrades': True,
                  'fetchOHLCV': True,
                  'fetchOpenOrder': None,
                  'fetchOpenOrders': True,
                  'fetchOrder': True,
                  'fetchOrderBook': True,
                  'fetchOrderBooks': None,
                  'fetchOrders': None,
                  'fetchOrderTrades': True,
                  'fetchPosition': True,
                  'fetchPositions': True,
                  'fetchPositionsRisk': False,
                  'fetchPremiumIndexOHLCV': False,
                  'fetchStatus': True,
                  'fetchTicker': True,
                  'fetchTickers': True,
                  'fetchTime': True,
                  'fetchTrades': True,
                  'fetchTradingFee': True,
                  'fetchTradingFees': None,
                  'fetchTradingLimits': None,
                  'fetchTransactions': None,
                  'fetchTransfers': None,
                  'fetchWithdrawal': None,
                  'fetchWithdrawals': True,
                  'fetchWithdrawalWhitelist': None,
                  'loadLeverageBrackets': None,
                  'reduceMargin': True,
                  'setLeverage': True,
                  'setMarginMode': True,
                  'setPositionMode': True,
                  'signIn': None,
                  'transfer': True,
                  'withdraw': True,
              },
              'timeframes': {
                  '1m': '1m',
                  '3m': '3m',
                  '5m': '5m',
                  '15m': '15m',
                  '30m': '30m',
                  '1h': '1H',
                  '2h': '2H',
                  '4h': '4H',
                  '6h': '6Hutc',
                  '12h': '12Hutc',
                  '1d': '1Dutc',
                  '1w': '1Wutc',
                  '1M': '1Mutc',
                  '3M': '3Mutc',
                  '6M': '6Mutc',
                  '1y': '1Yutc',
              },
              'hostname': 'www.okex.com',
              'urls': {
                  'logo': 'https://user-images.githubusercontent.com/1294454/32552768-0d6dd3c6-c4a6-11e7-90f8-c043b64756a7.jpg',
                  'api': {
                      'rest': 'https://{hostname}',
                  },
                  'www': 'https://www.okex.com',
                  'doc': 'https://www.okex.com/docs-v5/en/',
                  'fees': 'https://www.okex.com/pages/products/fees.html',
                  # 'referral': 'https://www.okex.com/join/1888677',
                  'test': {
                      'rest': 'https://testnet.okex.com',
                  },
              },
              'api': {
                  'public': {
                      'get': {
                          'market/tickers': 1,
                          'market/ticker': 1,
                          'market/index-tickers': 1,
                          'market/books': 1,
                          'market/candles': 1,
                          'market/history-candles': 1,
                          'market/index-candles': 1,
                          'market/mark-price-candles': 1,
                          'market/trades': 1,
                          'market/platform-24-volume': 10,
                          'market/open-oracle': 100,
                          'market/index-components': 1,
                          # 'market/oracle',
                          'public/instruments': 1,
                          'public/delivery-exercise-history': 0.5,
                          'public/open-interest': 1,
                          'public/funding-rate': 1,
                          'public/funding-rate-history': 1,
                          'public/price-limit': 1,
                          'public/opt-summary': 1,
                          'public/estimated-price': 2,
                          'public/discount-rate-interest-free-quota': 10,
                          'public/time': 2,
                          'public/liquidation-orders': 0.5,
                          'public/mark-price': 2,
                          # 'public/tier',
                          'public/position-tiers': 2,
                          'public/underlying': 1,
                          'public/interest-rate-loan-quota': 10,
                          'rubik/stat/trading-data/support-coin': 4,
                          'rubik/stat/taker-volume': 4,
                          'rubik/stat/margin/loan-ratio': 4,
                          # long/short
                          'rubik/stat/contracts/long-short-account-ratio': 4,
                          'rubik/stat/contracts/open-interest-volume': 4,
                          'rubik/stat/option/open-interest-volume': 4,
                          # put/call
                          'rubik/stat/option/open-interest-volume-ratio': 4,
                          'rubik/stat/option/open-interest-volume-expiry': 4,
                          'rubik/stat/option/open-interest-volume-strike': 4,
                          'rubik/stat/option/taker-block-volume': 4,
                          'system/status': 100,
                          'asset/lending-rate-summary': 5 / 3,
                          'asset/lending-rate-history': 5 / 3,
                          'market/exchange-rate': 20,
                      },
                  },
                  'private': {
                      'get': {
                          'account/account-position-risk': 2,
                          'account/balance': 2,
                          'account/positions': 2,
                          'account/bills': 5 / 3,
                          'account/bills-archive': 5 / 3,
                          'account/config': 4,
                          'account/max-size': 1,
                          'account/max-avail-size': 1,
                          'account/leverage-info': 1,
                          'account/max-loan': 1,
                          'account/trade-fee': 4,
                          'account/interest-accrued': 4,
                          'account/interest-rate': 4,
                          'account/max-withdrawal': 1,
                          'account/risk-state': 2,
                          'account/borrow-repay-history': 4,
                          'account/interest-limits': 4,
                          'asset/asset-valuation': 1 / 5,
                          'asset/deposit-address': 5 / 3,
                          'asset/balances': 5 / 3,
                          'asset/transfer-state': 10,
                          'asset/deposit-history': 5 / 3,
                          'asset/withdrawal-history': 5 / 3,
                          'asset/currencies': 5 / 3,
                          'asset/bills': 5 / 3,
                          'asset/piggy-balance': 5 / 3,
                          'asset/deposit-lightning': 5,
                          'asset/lending-history': 5 / 3,
                          'asset/saving-balance': 5 / 3,
                          'trade/order': 1 / 3,
                          'trade/orders-pending': 1,
                          'trade/orders-history': 0.5,
                          'trade/orders-history-archive': 1,
                          'trade/fills': 1 / 3,
                          'trade/fills-history': 2,
                          'trade/orders-algo-pending': 1,
                          'trade/orders-algo-history': 1,
                          'account/subaccount/balances': 10,
                          'asset/subaccount/bills': 5 / 3,
                          'users/subaccount/list': 10,
                          'users/subaccount/apikey': 10,
                          # broker
                          'broker/nd/info': 10,
                          'broker/nd/subaccount-info': 10,
                          'asset/broker/nd/subaccount-deposit-address': 4,
                          'asset/broker/nd/subaccount-deposit-history': 4,
                          'broker/nd/rebate-daily': 1,
                      },
                      'post': {
                          'account/set-position-mode': 4,
                          'account/set-leverage': 1,
                          'account/position/margin-balance': 1,
                          'account/set-greeks': 4,
                          'account/set-isolated-mode': 4,
                          'account/simulated_margin': 10,
                          'account/borrow-repay': 5 / 3,
                          'asset/transfer': 10,
                          'asset/withdrawal': 5 / 3,
                          'asset/purchase_redempt': 5 / 3,
                          'asset/withdrawal-lightning': 5,
                          'asset/set-lending-rate': 5 / 3,
                          'trade/order': 1 / 3,
                          'trade/batch-orders': 1 / 15,
                          'trade/cancel-order': 1 / 3,
                          'trade/cancel-batch-orders': 1 / 15,
                          'trade/amend-order': 1 / 3,
                          'trade/amend-batch-orders': 1 / 3,
                          'trade/close-position': 1,
                          'trade/order-algo': 1,
                          'trade/cancel-algos': 1,
                          'trade/cancel-advance-algos': 1,
                          'users/subaccount/delete-apikey': 10,
                          'users/subaccount/modify-apikey': 10,
                          'users/subaccount/apikey': 10,
                          'asset/subaccount/transfer': 10,
                          # broker
                          'broker/nd/create-subaccount': 10,
                          'broker/nd/delete-subaccount': 10,
                          'broker/nd/set-subaccount-level': 4,
                          'broker/nd/set-subaccount-fee-rate': 4,
                          'asset/broker/nd/subaccount-deposit-address': 4,
                      },
                  },
              },
              'fees': {
                  'trading': {
                      'taker': self.parse_number('0.0015'),
                      'maker': self.parse_number('0.0010'),
                  },
                  'spot': {
                      'taker': self.parse_number('0.0015'),
                      'maker': self.parse_number('0.0010'),
                  },
                  'futures': {
                      'taker': self.parse_number('0.0005'),
                      'maker': self.parse_number('0.0002'),
                  },
                  'swap': {
                      'taker': self.parse_number('0.00050'),
                      'maker': self.parse_number('0.00020'),
                  },
              },
              'requiredCredentials': {
                  'apiKey': True,
                  'secret': True,
                  'password': True,
              },
              'exceptions': {
                  'exact': {
                      # Public error codes from 50000-53999
                      # General Class
                      '1': ExchangeError,  # Operation failed
                      '2': ExchangeError,  # Bulk operation partially succeeded
                      '50000': BadRequest,  # Body can not be empty
                      '50001': OnMaintenance,  # Matching engine upgrading. Please try again later
                      '50002': BadRequest,  # Json data format error
                      '50004': RequestTimeout,  # Endpoint request timeout(does not indicate success or failure of order, please check order status)
                      '50005': ExchangeNotAvailable,  # API is offline or unavailable
                      '50006': BadRequest,  # Invalid Content_Type, please use "application/json" format
                      '50007': AccountSuspended,  # Account blocked
                      '50008': AuthenticationError,  # User does not exist
                      '50009': AccountSuspended,  # Account is suspended due to ongoing liquidation
                      '50010': ExchangeError,  # User ID can not be empty
                      '50011': RateLimitExceeded,  # Request too frequent
                      '50012': ExchangeError,  # Account status invalid
                      '50013': ExchangeNotAvailable,  # System is busy, please try again later
                      '50014': 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': ExchangeError,  # System error
                      '50027': PermissionDenied,  # The account is restricted from trading
                      '50028': ExchangeError,  # Unable to take the order, please reach out to support center for details
                      # API Class
                      '50100': ExchangeError,  # API frozen, please contact customer service
                      '50101': 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': InsufficientFunds,  # Account level too low
                      '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
                      '51021': BadSymbol,  # Contract to be listed
                      '51022': BadSymbol,  # Contract suspended
                      '51023': ExchangeError,  # Position does not exist
                      '51024': AccountSuspended,  # Unified accountblocked
                      '51025': ExchangeError,  # Order count exceeds the limit
                      '51026': BadSymbol,  # Instrument type does not match underlying index
                      '51027': BadSymbol,  # Contract expired
                      '51028': BadSymbol,  # Contract under delivery
                      '51029': BadSymbol,  # Contract is being settled
                      '51030': BadSymbol,  # Funding fee is being settled
                      '51031': InvalidOrder,  # This order price is not within the closing price range
                      '51100': InvalidOrder,  # Trading amount does not meet the min tradable amount
                      '51101': InvalidOrder,  # Entered amount exceeds the max pending order amount(Cont) per transaction
                      '51102': InvalidOrder,  # Entered amount exceeds the max pending count
                      '51103': InvalidOrder,  # Entered amount exceeds the max pending order count of the underlying asset
                      '51104': InvalidOrder,  # Entered amount exceeds the max pending order amount(Cont) of the underlying asset
                      '51105': InvalidOrder,  # Entered amount exceeds the max order amount(Cont) of the contract
                      '51106': InvalidOrder,  # Entered amount exceeds the max order amount(Cont) of the underlying asset
                      '51107': InvalidOrder,  # Entered amount exceeds the max holding amount(Cont)
                      '51108': InvalidOrder,  # Positions exceed the limit for closing out with the market price
                      '51109': InvalidOrder,  # No available offer
                      '51110': InvalidOrder,  # You can only place a limit order after Call Auction has started
                      '51111': BadRequest,  # Maximum {0} orders can be placed in bulk
                      '51112': InvalidOrder,  # Close order size exceeds your available size
                      '51113': RateLimitExceeded,  # Market-price liquidation requests too frequent
                      '51115': InvalidOrder,  # Cancel all pending close-orders before liquidation
                      '51116': InvalidOrder,  # Order price or trigger price exceeds {0}
                      '51117': InvalidOrder,  # Pending close-orders count exceeds limit
                      '51118': InvalidOrder,  # Total amount should exceed the min amount per order
                      '51119': InsufficientFunds,  # Order placement failed due to insufficient balance
                      '51120': InvalidOrder,  # Order quantity is less than {0}, please try again
                      '51121': InvalidOrder,  # Order count should be the integer multiples of the lot size
                      '51122': InvalidOrder,  # Order price should be higher than the min price {0}
                      '51124': InvalidOrder,  # You can only place limit orders during call auction
                      '51125': InvalidOrder,  # Currently there are reduce + reverse position pending orders in margin trading. Please cancel all reduce + reverse position pending orders and continue
                      '51126': InvalidOrder,  # Currently there are reduce only pending orders in margin trading.Please cancel all reduce only pending orders and continue
                      '51127': InsufficientFunds,  # Available balance is 0
                      '51128': InvalidOrder,  # Multi-currency margin account can not do cross-margin trading
                      '51129': InvalidOrder,  # The value of the position and buy order has reached the position limit, and no further buying is allowed
                      '51130': BadSymbol,  # Fixed margin currency error
                      '51131': InsufficientFunds,  # Insufficient balance
                      '51132': InvalidOrder,  # Your position amount is negative and less than the minimum trading amount
                      '51133': InvalidOrder,  # Reduce-only feature is unavailable for the spot transactions by multi-currency margin account
                      '51134': InvalidOrder,  # Closing failed. Please check your holdings and pending orders
                      '51135': InvalidOrder,  # Your closing price has triggered the limit price, and the max buy price is {0}
                      '51136': InvalidOrder,  # Your closing price has triggered the limit price, and the min sell price is {0}
                      '51137': InvalidOrder,  # Your opening price has triggered the limit price, and the max buy price is {0}
                      '51138': InvalidOrder,  # Your opening price has triggered the limit price, and the min sell price is {0}
                      '51139': InvalidOrder,  # Reduce-only feature is unavailable for the spot transactions by simple account
                      '51201': InvalidOrder,  # Value of per market order cannot exceed 100,000 USDT
                      '51202': InvalidOrder,  # Market - order amount exceeds the max amount
                      '51203': InvalidOrder,  # Order amount exceeds the limit {0}
                      '51204': InvalidOrder,  # The price for the limit order can not be empty
                      '51205': InvalidOrder,  # Reduce-Only is not available
                      '51250': InvalidOrder,  # Algo order price is out of the available range
                      '51251': InvalidOrder,  # Algo order type error(when user place an iceberg order)
                      '51252': InvalidOrder,  # Algo order price is out of the available range
                      '51253': InvalidOrder,  # Average amount exceeds the limit of per iceberg order
                      '51254': InvalidOrder,  # Iceberg average amount error(when user place an iceberg order)
                      '51255': InvalidOrder,  # Limit of per iceberg order: Total amount/1000 < x <= Total amount
                      '51256': InvalidOrder,  # Iceberg order price variance error
                      '51257': InvalidOrder,  # Trail order callback rate error
                      '51258': InvalidOrder,  # Trail - order placement failed. The trigger price of a sell order should be higher than the last transaction price
                      '51259': InvalidOrder,  # Trail - order placement failed. The trigger price of a buy order should be lower than the last transaction price
                      '51260': InvalidOrder,  # Maximum {0} pending trail - orders can be held at the same time
                      '51261': InvalidOrder,  # Each user can hold up to {0} pending stop - orders at the same time
                      '51262': InvalidOrder,  # Maximum {0} pending iceberg orders can be held at the same time
                      '51263': InvalidOrder,  # Maximum {0} pending time-weighted orders can be held at the same time
                      '51264': InvalidOrder,  # Average amount exceeds the limit of per time-weighted order
                      '51265': InvalidOrder,  # Time-weighted order limit error
                      '51267': InvalidOrder,  # Time-weighted order strategy initiative rate error
                      '51268': InvalidOrder,  # Time-weighted order strategy initiative range error
                      '51269': InvalidOrder,  # Time-weighted order interval error, the interval should be {0}<= x<={1}
                      '51270': InvalidOrder,  # The limit of time-weighted order price variance is 0 < x <= 1%
                      '51271': InvalidOrder,  # Sweep ratio should be 0 < x <= 100%
                      '51272': InvalidOrder,  # Price variance should be 0 < x <= 1%
                      '51273': InvalidOrder,  # Total amount should be more than {0}
                      '51274': InvalidOrder,  # Total quantity of time-weighted order must be larger than single order limit
                      '51275': InvalidOrder,  # The amount of single stop-market order can not exceed the upper limit
                      '51276': InvalidOrder,  # Stop - Market orders cannot specify a price
                      '51277': InvalidOrder,  # TP trigger price can not be higher than the last price
                      '51278': InvalidOrder,  # SL trigger price can not be lower than the last price
                      '51279': InvalidOrder,  # TP trigger price can not be lower than the last price
                      '51280': InvalidOrder,  # SL trigger price can not be higher than the last price
                      '51400': OrderNotFound,  # Cancellation failed as the order does not exist
                      '51401': OrderNotFound,  # Cancellation failed as the order is already canceled
                      '51402': OrderNotFound,  # Cancellation failed as the order is already completed
                      '51403': InvalidOrder,  # Cancellation failed as the order type does not support cancellation
                      '51404': InvalidOrder,  # Order cancellation unavailable during the second phase of call auction
                      '51405': ExchangeError,  # Cancellation failed as you do not have any pending orders
                      '51406': ExchangeError,  # Canceled - order count exceeds the limit {0}
                      '51407': BadRequest,  # Either order ID or client order ID is required
                      '51408': ExchangeError,  # Pair ID or name does not match the order info
                      '51409': ExchangeError,  # Either pair ID or pair name ID is required
                      '51410': CancelPending,  # Cancellation failed as the order is already under cancelling status
                      '51500': ExchangeError,  # Either order price or amount is required
                      '51501': ExchangeError,  # Maximum {0} orders can be modified
                      '51502': InsufficientFunds,  # Order modification failed for insufficient margin
                      '51503': ExchangeError,  # Order modification failed as the order does not exist
                      '51506': ExchangeError,  # Order modification unavailable for the order type
                      '51508': ExchangeError,  # Orders are not allowed to be modified during the call auction
                      '51509': ExchangeError,  # Modification failed as the order has been canceled
                      '51510': ExchangeError,  # Modification failed as the order has been completed
                      '51511': ExchangeError,  # Modification failed as the order price did not meet the requirement for Post Only
                      '51600': ExchangeError,  # Status not found
                      '51601': ExchangeError,  # Order status and order ID cannot exist at the same time
                      '51602': ExchangeError,  # Either order status or order ID is required
                      '51603': OrderNotFound,  # Order does not exist
                      # Data class
                      '52000': ExchangeError,  # No updates
                      # SPOT/MARGIN error codes 54000-54999
                      '54000': ExchangeError,  # Margin transactions unavailable
                      '54001': ExchangeError,  # Only Multi-currency margin account can be set to borrow coins automatically
                      # FUNDING error codes 58000-58999
                      '58000': ExchangeError,  # Account type {0} does not supported when getting the sub-account balance
                      '58001': AuthenticationError,  # Incorrect trade password
                      '58002': PermissionDenied,  # Please activate Savings Account first
                      '58003': ExchangeError,  # Currency type is not supported by Savings Account
                      '58004': AccountSuspended,  # Account blocked(transfer & withdrawal endpoint: either end of the account does not authorize the transfer)
                      '58005': ExchangeError,  # The redeemed amount must be no greater than {0}
                      '58006': ExchangeError,  # Service unavailable for token {0}
                      '58007': ExchangeError,  # Abnormal Assets interface. Please try again later
                      '58100': ExchangeError,  # The trading product triggers risk control, and the platform has suspended the fund transfer-out function with related users. Please wait patiently
                      '58101': AccountSuspended,  # Transfer suspended(transfer endpoint: either end of the account does not authorize the transfer)
                      '58102': RateLimitExceeded,  # Too frequent transfer(transfer too frequently)
                      '58103': ExchangeError,  # Parent account user id does not match sub-account user id
                      '58104': ExchangeError,  # Since your P2P transaction is abnormal, you are restricted from making fund transfers. Please contact customer support to remove the restriction
                      '58105': ExchangeError,  # Since your P2P transaction is abnormal, you are restricted from making fund transfers. Please transfer funds on our website or app to complete identity verification
                      '58106': ExchangeError,  # Please enable the account for spot contract
                      '58107': ExchangeError,  # Please enable the account for futures contract
                      '58108': ExchangeError,  # Please enable the account for option contract
                      '58109': ExchangeError,  # Please enable the account for swap contract
                      '58110': ExchangeError,  # The contract triggers risk control, and the platform has suspended the fund transfer function of it. Please wait patiently
                      '58111': ExchangeError,  # Funds transfer unavailable as the perpetual contract is charging the funding fee. Please try again later
                      '58112': ExchangeError,  # Your fund transfer failed. Please try again later
                      '58114': ExchangeError,  # Transfer amount must be more than 0
                      '58115': ExchangeError,  # Sub-account does not exist
                      '58116': ExchangeError,  # Transfer amount exceeds the limit
                      '58117': ExchangeError,  # Account assets are abnormal, please deal with negative assets before transferring
                      '58200': ExchangeError,  # Withdrawal from {0} to {1} is unavailable for self currency
                      '58201': ExchangeError,  # Withdrawal amount exceeds the daily limit
                      '58202': ExchangeError,  # The minimum withdrawal amount for NEO is 1, and the amount must be an integer
                      '58203': InvalidAddress,  # Please add a withdrawal address
                      '58204': AccountSuspended,  # Withdrawal suspended
                      '58205': ExchangeError,  # Withdrawal amount exceeds the upper limit
                      '58206': ExchangeError,  # Withdrawal amount is lower than the lower limit
                      '58207': InvalidAddress,  # Withdrawal failed due to address error
                      '58208': ExchangeError,  # Withdrawal failed. Please link your email
                      '58209': ExchangeError,  # Withdrawal failed. Withdraw feature is not available for sub-accounts
                      '58210': ExchangeError,  # Withdrawal fee exceeds the upper limit
                      '58211': ExchangeError,  # Withdrawal fee is lower than the lower limit(withdrawal endpoint: incorrect fee)
                      '58212': ExchangeError,  # Withdrawal fee should be {0}% of the withdrawal amount
                      '58213': AuthenticationError,  # Please set trading password before withdrawal
                      '58300': ExchangeError,  # Deposit-address count exceeds the limit
                      '58350': InsufficientFunds,  # Insufficient balance
                      # Account error codes 59000-59999
                      '59000': ExchangeError,  # Your settings failed as you have positions or open orders
                      '59001': ExchangeError,  # Switching unavailable as you have borrowings
                      '59100': ExchangeError,  # You have open positions. Please cancel all open positions before changing the leverage
                      '59101': ExchangeError,  # You have pending orders with isolated positions. Please cancel all the pending orders and adjust the leverage
                      '59102': ExchangeError,  # Leverage exceeds the maximum leverage. Please adjust the leverage
                      '59103': InsufficientFunds,  # Leverage is too low and no sufficient margin in your account. Please adjust the leverage
                      '59104': ExchangeError,  # The leverage is too high. The borrowed position has exceeded the maximum position of self leverage. Please adjust the leverage
                      '59105': ExchangeError,  # Leverage can not be less than {0}. Please adjust the leverage
                      '59106': ExchangeError,  # The max available margin corresponding to your order tier is {0}. Please adjust your margin and place a new order
                      '59107': ExchangeError,  # You have pending orders under the service, please modify the leverage after canceling all pending orders
                      '59108': InsufficientFunds,  # Low leverage and insufficient margin, please adjust the leverage
                      '59109': ExchangeError,  # Account equity less than the required margin amount after adjustment. Please adjust the leverage
                      '59200': InsufficientFunds,  # Insufficient account balance
                      '59201': InsufficientFunds,  # Negative account balance
                      '59300': ExchangeError,  # Margin call failed. Position does not exist
                      '59301': ExchangeError,  # Margin adjustment failed for exceeding the max limit
                      '59401': ExchangeError,  # Holdings already reached the limit
                      '59500': ExchangeError,  # Only the APIKey of the main account has permission
                      '59501': ExchangeError,  # Only 50 APIKeys can be created per account
                      '59502': ExchangeError,  # Note name cannot be duplicate with the currently created APIKey note name
                      '59503': ExchangeError,  # Each APIKey can bind up to 20 IP addresses
                      '59504': ExchangeError,  # The sub account does not support the withdrawal function
                      '59505': ExchangeError,  # The passphrase format is incorrect
                      '59506': ExchangeError,  # APIKey does not exist
                      '59507': ExchangeError,  # The two accounts involved in a transfer must be two different sub accounts under the same parent account
                      '59508': AccountSuspended,  # The sub account of {0} is suspended
                      # WebSocket error Codes from 60000-63999
                      '60001': AuthenticationError,  # "OK_ACCESS_KEY" can not be empty
                      '60002': AuthenticationError,  # "OK_ACCESS_SIGN" can not be empty
                      '60003': AuthenticationError,  # "OK_ACCESS_PASSPHRASE" can not be empty
                      '60004': AuthenticationError,  # Invalid OK_ACCESS_TIMESTAMP
                      '60005': AuthenticationError,  # Invalid OK_ACCESS_KEY
                      '60006': InvalidNonce,  # Timestamp request expired
                      '60007': AuthenticationError,  # Invalid sign
                      '60008': AuthenticationError,  # Login is not supported for public channels
                      '60009': AuthenticationError,  # Login failed
                      '60010': AuthenticationError,  # Already logged in
                      '60011': AuthenticationError,  # Please log in
                      '60012': BadRequest,  # Illegal request
                      '60013': BadRequest,  # Invalid args
                      '60014': RateLimitExceeded,  # Requests too frequent
                      '60015': NetworkError,  # Connection closed as there was no data transmission in the last 30 seconds
                      '60016': ExchangeNotAvailable,  # Buffer is full, cannot write data
                      '60017': BadRequest,  # Invalid url path
                      '60018': BadRequest,  # The {0} {1} {2} {3} {4} does not exist
                      '60019': BadRequest,  # Invalid op {op}
                      '63999': ExchangeError,  # Internal system error
                  },
                  'broad': {
                  },
              },
              'httpExceptions': {
                  '429': ExchangeNotAvailable,  # https://github.com/ccxt/ccxt/issues/9612
              },
              'precisionMode': TICK_SIZE,
              'options': {
                  'defaultNetwork': 'ERC20',
                  'networks': {
                      'ETH': 'ERC20',
                      'TRX': 'TRC20',
                      'OMNI': 'Omni',
                  },
                  'layerTwo': {
                      'Lightning': True,
                      'Liquid': True,
                  },
                  'fetchOHLCV': {
                      # 'type': 'Candles',  # Candles or HistoryCandles, IndexCandles, MarkPriceCandles
                  },
                  'createOrder': 'privatePostTradeBatchOrders',  # or 'privatePostTradeOrder'
                  'createMarketBuyOrderRequiresPrice': False,
                  'fetchMarkets': ['spot', 'futures', 'swap', 'option'],  # spot, futures, swap, option
                  'defaultType': 'spot',  # 'funding', 'spot', 'margin', 'futures', 'swap', 'option'
                  # 'fetchBalance': {
                  #     'type': 'spot',  # 'funding', 'trading', 'spot'
                  # },
                  'fetchLedger': {
                      'method': 'privateGetAccountBills',  # privateGetAccountBillsArchive, privateGetAssetBills
                  },
                  # 1 = SPOT, 3 = FUTURES, 5 = MARGIN, 6 = FUNDING, 9 = SWAP, 12 = OPTION, 18 = Unified account
                  'accountsByType': {
                      'spot': '1',
                      'futures': '3',
                      'margin': '5',
                      'funding': '6',
                      'swap': '9',
                      'option': '12',
                      'trading': '18',  # unified trading account
                      'unified': '18',
                  },
                  'typesByAccount': {
                      '1': 'spot',
                      '3': 'futures',
                      '5': 'margin',
                      '6': 'funding',
                      '9': 'swap',
                      '12': 'option',
                      '18': 'trading',  # unified trading account
                  },
                  'brokerId': BROKER_ID,
              },
              'commonCurrencies': {
                  # OKEX refers to ERC20 version of Aeternity(AEToken)
                  'AE': 'AET',  # https://github.com/ccxt/ccxt/issues/4981
                  'BOX': 'DefiBox',
                  'HOT': 'Hydro Protocol',
                  'HSR': 'HC',
                  'MAG': 'Maggie',
                  'SBTC': 'Super Bitcoin',
                  'TRADE': 'Unitrade',
                  'YOYO': 'YOYOW',
                  'WIN': 'WinToken',  # https://github.com/ccxt/ccxt/issues/5701
              },
          })
  
      def fetch_status(self, params={}):
          response = self.publicGetSystemStatus(params)
          #
          #     {
          #         "code":"0",
          #         "data":[
          #             {
          #                 "begin":"1621328400000",
          #                 "end":"1621329000000",
          #                 "href":"https://www.okex.com/support/hc/en-us/articles/360060882172",
          #                 "scheDesc":"",
          #                 "serviceType":"1",  # 0 WebSocket, 1 Spot/Margin, 2 Futures, 3 Perpetual, 4 Options, 5 Trading service
          #                 "state":"scheduled",  # ongoing, completed, canceled
          #                 "system":"classic",  # classic, unified
          #                 "title":"Classic Spot System Upgrade"
          #             },
          #         ],
          #         "msg":""
          #     }
          #
          data = self.safe_value(response, 'data', [])
          timestamp = self.milliseconds()
          update = {
              'info': response,
              'updated': timestamp,
              'status': 'ok',
              'eta': None,
          }
          for i in range(0, len(data)):
              event = data[i]
              state = self.safe_string(event, 'state')
              if state == 'ongoing':
                  update['eta'] = self.safe_integer(event, 'end')
                  update['status'] = 'maintenance'
          self.status = self.extend(self.status, update)
          return self.status
  
      def fetch_time(self, params={}):
          response = self.publicGetPublicTime(params)
          #
          #     {
          #         "code":"0",
          #         "data":[
          #             {"ts":"1621247923668"}
          #         ],
          #         "msg":""
          #     }
          #
          data = self.safe_value(response, 'data', [])
          first = self.safe_value(data, 0, {})
          return self.safe_integer(first, 'ts')
  
      def fetch_markets(self, params={}):
          types = self.safe_value(self.options, 'fetchMarkets')
          result = []
          for i in range(0, len(types)):
              markets = self.fetch_markets_by_type(types[i], params)
              result = self.array_concat(result, markets)
          return result
  
      def parse_markets(self, markets):
          result = []
          for i in range(0, len(markets)):
              result.append(self.parse_market(markets[i]))
          return result
  
      def parse_market(self, market):
          #
          #     {
          #         "alias":"",  # self_week, next_week, quarter, next_quarter
          #         "baseCcy":"BTC",
          #         "category":"1",
          #         "ctMult":"",
          #         "ctType":"",  # inverse, linear
          #         "ctVal":"",
          #         "ctValCcy":"",
          #         "expTime":"",
          #         "instId":"BTC-USDT",  # BTC-USD-210521, CSPR-USDT-SWAP, BTC-USD-210517-44000-C
          #         "instType":"SPOT",  # SPOT, FUTURES, SWAP, OPTION
          #         "lever":"10",
          #         "listTime":"1548133413000",
          #         "lotSz":"0.00000001",
          #         "minSz":"0.00001",
          #         "optType":"",
          #         "quoteCcy":"USDT",
          #         "settleCcy":"",
          #         "state":"live",
          #         "stk":"",
          #         "tickSz":"0.1",
          #         "uly":""
          #     }
          #
          #     {
          #         alias: "",
          #         baseCcy: "",
          #         category: "1",
          #         ctMult: "0.1",
          #         ctType: "",
          #         ctVal: "1",
          #         ctValCcy: "BTC",
          #         expTime: "1648195200000",
          #         instId: "BTC-USD-220325-194000-P",
          #         instType: "OPTION",
          #         lever: "",
          #         listTime: "1631262612280",
          #         lotSz: "1",
          #         minSz: "1",
          #         optType: "P",
          #         quoteCcy: "",
          #         settleCcy: "BTC",
          #         state: "live",
          #         stk: "194000",
          #         tickSz: "0.0005",
          #         uly: "BTC-USD"
          #     }
          #
          id = self.safe_string(market, 'instId')
          type = self.safe_string_lower(market, 'instType')
          spot = (type == 'spot')
          futures = (type == 'futures')
          swap = (type == 'swap')
          option = (type == 'option')
          contract = swap or futures or option
          baseId = self.safe_string(market, 'baseCcy')
          quoteId = self.safe_string(market, 'quoteCcy')
          settleId = self.safe_string(market, 'settleCcy')
          settle = self.safe_currency_code(settleId)
          underlying = self.safe_string(market, 'uly')
          if (underlying is not None) and not spot:
              parts = underlying.split('-')
              baseId = self.safe_string(parts, 0)
              quoteId = self.safe_string(parts, 1)
          base = self.safe_currency_code(baseId)
          quote = self.safe_currency_code(quoteId)
          symbol = base + '/' + quote
          expiry = None
          strikePrice = None
          optionType = None
          if contract:
              symbol = symbol + ':' + settle
              expiry = self.safe_integer(market, 'expTime')
              if expiry is not None:
                  ymd = self.yymmdd(expiry)
                  symbol = symbol + '-' + ymd
              if option:
                  strikePrice = self.safe_string(market, 'stk')
                  optionType = self.safe_string(market, 'optType')
                  symbol = symbol + '-' + strikePrice + '-' + optionType
                  optionType = 'put' if (optionType == 'P') else 'call'
          tickSize = self.safe_string(market, 'tickSz')
          minAmountString = self.safe_string(market, 'minSz')
          minAmount = self.parse_number(minAmountString)
          fees = self.safe_value_2(self.fees, type, 'trading', {})
          precisionPrice = self.parse_number(tickSize)
          maxLeverage = self.safe_string(market, 'lever', '1')
          if maxLeverage == '':
              maxLeverage = '1'
          maxLeverage = Precise.string_max(maxLeverage, '1')
          return self.extend(fees, {
              'id': id,
              'symbol': symbol,
              'base': base,
              'quote': quote,
              'settle': settle,
              'baseId': baseId,
              'quoteId': quoteId,
              'settleId': settleId,
              'type': type,
              'spot': spot,
              'margin': spot and (Precise.string_gt(maxLeverage, '1')),
              'swap': swap,
              'futures': futures,
              'option': option,
              'active': True,
              'contract': contract,
              'linear': (quoteId == settleId) if contract else None,
              'inverse': (baseId == settleId) if contract else None,
              'contractSize': self.safe_number(market, 'ctVal') if contract else None,
              'expiry': expiry,
              'expiryDatetime': self.iso8601(expiry),
              'strike': strikePrice,
              'optionType': optionType,
              'precision': {
                  'price': precisionPrice,
                  'amount': self.safe_number(market, 'lotSz'),
              },
              'limits': {
                  'leverage': {
                      'min': self.parse_number('1'),
                      'max': self.parse_number(maxLeverage),
                  },
                  'amount': {
                      'min': minAmount,
                      'max': None,
                  },
                  'price': {
                      'min': precisionPrice,
                      'max': None,
                  },
                  'cost': {
                      'min': None,
                      'max': None,
                  },
              },
              'info': market,
          })
  
      def fetch_markets_by_type(self, type, params={}):
          uppercaseType = type.upper()
          request = {
              'instType': uppercaseType,
          }
          if uppercaseType == 'OPTION':
              defaultUnderlying = self.safe_value(self.options, 'defaultUnderlying', 'BTC-USD')
              currencyId = self.safe_string_2(params, 'uly', 'marketId', defaultUnderlying)
              if currencyId is None:
                  raise ArgumentsRequired(self.id + ' fetchMarketsByType requires an underlying uly or marketId parameter for options markets')
              else:
                  request['uly'] = currencyId
          response = self.publicGetPublicInstruments(self.extend(request, params))
          #
          # spot, futures, swaps, options
          #
          #     {
          #         "code":"0",
          #         "data":[
          #             {
          #                 "alias":"",  # self_week, next_week, quarter, next_quarter
          #                 "baseCcy":"BTC",
          #                 "category":"1",
          #                 "ctMult":"",
          #                 "ctType":"",  # inverse, linear
          #                 "ctVal":"",
          #                 "ctValCcy":"",
          #                 "expTime":"",
          #                 "instId":"BTC-USDT",  # BTC-USD-210521, CSPR-USDT-SWAP, BTC-USD-210517-44000-C
          #                 "instType":"SPOT",  # SPOT, FUTURES, SWAP, OPTION
          #                 "lever":"10",
          #                 "listTime":"1548133413000",
          #                 "lotSz":"0.00000001",
          #                 "minSz":"0.00001",
          #                 "optType":"",
          #                 "quoteCcy":"USDT",
          #                 "settleCcy":"",
          #                 "state":"live",
          #                 "stk":"",
          #                 "tickSz":"0.1",
          #                 "uly":""
          #             }
          #         ],
          #         "msg":""
          #     }
          #
          data = self.safe_value(response, 'data', [])
          return self.parse_markets(data)
  
      def safe_network(self, networkId):
          networksById = {
              'Bitcoin': 'BTC',
              'Omni': 'OMNI',
              'TRON': 'TRC20',
          }
          return self.safe_string(networksById, networkId, networkId)
  
      def fetch_currencies(self, params={}):
          # self endpoint requires authentication
          # while fetchCurrencies is a public API method by design
          # therefore we check the keys here
          # and fallback to generating the currencies from the markets
          if not self.check_required_credentials(False):
              return None
          # has['fetchCurrencies'] is currently set to False
          # it will reply with {"msg":"Request header “OK_ACCESS_KEY“ can't be empty.","code":"50103"}
          # if you attempt to access it without authentication
          response = self.privateGetAssetCurrencies(params)
          #
          #     {
          #         "code":"0",
          #         "data":[
          #             {
          #                 "canDep":true,
          #                 "canInternal":true,
          #                 "canWd":true,
          #                 "ccy":"USDT",
          #                 "chain":"USDT-ERC20",
          #                 "maxFee":"40",
          #                 "minFee":"20",
          #                 "minWd":"2",
          #                 "name":""
          #             }
          #         ],
          #         "msg":""
          #     }
          #
          data = self.safe_value(response, 'data', [])
          result = {}
          dataByCurrencyId = self.group_by(data, 'ccy')
          currencyIds = list(dataByCurrencyId.keys())
          precision = self.parse_number('0.00000001')  # default precision, todo: fix "magic constants"
          for i in range(0, len(currencyIds)):
              currencyId = currencyIds[i]
              currency = self.safe_currency(currencyId)
              code = currency['code']
              chains = dataByCurrencyId[currencyId]
              networks = {}
              currencyActive = False
              depositEnabled = None
              withdrawEnabled = None
              for j in range(0, len(chains)):
                  chain = chains[j]
                  canDeposit = self.safe_value(chain, 'canDep')
                  canWithdraw = self.safe_value(chain, 'canWd')
                  canInternal = self.safe_value(chain, 'canInternal')
                  active = True if (canDeposit and canWithdraw and canInternal) else False
                  currencyActive = active if (currencyActive is None) else currencyActive
                  networkId = self.safe_string(chain, 'chain')
                  if canDeposit and not depositEnabled:
                      depositEnabled = True
                  elif not canDeposit:
                      depositEnabled = False
                  if canWithdraw and not withdrawEnabled:
                      withdrawEnabled = True
                  elif not canWithdraw:
                      withdrawEnabled = False
                  if networkId.find('-') >= 0:
                      parts = networkId.split('-')
                      chainPart = self.safe_string(parts, 1, networkId)
                      network = self.safe_network(chainPart)
                      mainNet = self.safe_value(chain, 'mainNet', False)
                      layerTwo = self.safe_value(self.options, 'layerTwo', {
                          'Liquid': True,
                          'Lightning': True,
                      })
                      if mainNet and not (chainPart in layerTwo):
                          # BTC lighting and liquid are both mainnet but not the same as BTC-Bitcoin
                          network = code
                      networks[network] = {
                          'info': chain,
                          'id': networkId,
                          'network': network,
                          'active': active,
                          'deposit': canDeposit,
                          'withdraw': canWithdraw,
                          'fee': self.safe_number(chain, 'minFee'),
                          'precision': None,
                          'limits': {
                              'withdraw': {
                                  'min': self.safe_number(chain, 'minWd'),
                                  'max': None,
                              },
                          },
                      }
              result[code] = {
                  'info': None,
                  'code': code,
                  'id': currencyId,
                  'name': None,
                  'active': currencyActive,
                  'deposit': depositEnabled,
                  'withdraw': withdrawEnabled,
                  'fee': None,
                  'precision': precision,
                  'limits': {
                      'amount': {
                          'min': None,
                          'max': None,
                      },
                  },
                  'networks': networks,
              }
          return result
  
      def fetch_order_book(self, symbol, limit=None, params={}):
          self.load_markets()
          market = self.market(symbol)
          request = {
              'instId': market['id'],
          }
          limit = 20 if (limit is None) else limit
          if limit is not None:
              request['sz'] = limit  # max 400
          response = self.publicGetMarketBooks(self.extend(request, params))
          #
          #     {
          #         "code":"0",
          #         "msg":"",
          #         "data":[
          #             {
          #                 "asks":[
          #                     ["0.07228","4.211619","0","2"],  # price, amount, liquidated orders, total open orders
          #                     ["0.0723","299.880364","0","2"],
          #                     ["0.07231","3.72832","0","1"],
          #                 ],
          #                 "bids":[
          #                     ["0.07221","18.5","0","1"],
          #                     ["0.0722","18.5","0","1"],
          #                     ["0.07219","0.505407","0","1"],
          #                 ],
          #                 "ts":"1621438475342"
          #             }
          #         ]
          #     }
          #
          data = self.safe_value(response, 'data', [])
          first = self.safe_value(data, 0, {})
          timestamp = self.safe_integer(first, 'ts')
          return self.parse_order_book(first, symbol, timestamp)
  
      def parse_ticker(self, ticker, market=None):
          #
          #     {
          #         "instType":"SPOT",
          #         "instId":"ETH-BTC",
          #         "last":"0.07319",
          #         "lastSz":"0.044378",
          #         "askPx":"0.07322",
          #         "askSz":"4.2",
          #         "bidPx":"0.0732",
          #         "bidSz":"6.050058",
          #         "open24h":"0.07801",
          #         "high24h":"0.07975",
          #         "low24h":"0.06019",
          #         "volCcy24h":"11788.887619",
          #         "vol24h":"167493.829229",
          #         "ts":"1621440583784",
          #         "sodUtc0":"0.07872",
          #         "sodUtc8":"0.07345"
          #     }
          #
          timestamp = self.safe_integer(ticker, 'ts')
          marketId = self.safe_string(ticker, 'instId')
          market = self.safe_market(marketId, market, '-')
          symbol = market['symbol']
          last = self.safe_string(ticker, 'last')
          open = self.safe_string(ticker, 'open24h')
          quoteVolume = self.safe_string(ticker, 'volCcy24h')
          baseVolume = self.safe_string(ticker, 'vol24h')
          return self.safe_ticker({
              'symbol': symbol,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'high': self.safe_string(ticker, 'high24h'),
              'low': self.safe_string(ticker, 'low24h'),
              '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, False)
  
      def fetch_ticker(self, symbol, params={}):
          self.load_markets()
          market = self.market(symbol)
          request = {
              'instId': market['id'],
          }
          response = self.publicGetMarketTicker(self.extend(request, params))
          #
          #     {
          #         "code":"0",
          #         "msg":"",
          #         "data":[
          #             {
          #                 "instType":"SPOT",
          #                 "instId":"ETH-BTC",
          #                 "last":"0.07319",
          #                 "lastSz":"0.044378",
          #                 "askPx":"0.07322",
          #                 "askSz":"4.2",
          #                 "bidPx":"0.0732",
          #                 "bidSz":"6.050058",
          #                 "open24h":"0.07801",
          #                 "high24h":"0.07975",
          #                 "low24h":"0.06019",
          #                 "volCcy24h":"11788.887619",
          #                 "vol24h":"167493.829229",
          #                 "ts":"1621440583784",
          #                 "sodUtc0":"0.07872",
          #                 "sodUtc8":"0.07345"
          #             }
          #         ]
          #     }
          #
          data = self.safe_value(response, 'data', [])
          first = self.safe_value(data, 0, {})
          return self.parse_ticker(first, market)
  
      def fetch_tickers_by_type(self, type, symbols=None, params={}):
          self.load_markets()
          uppercaseType = type.upper()
          request = {
              'instType': type.upper(),
          }
          if uppercaseType == 'OPTION':
              defaultUnderlying = self.safe_value(self.options, 'defaultUnderlying', 'BTC-USD')
              currencyId = self.safe_string_2(params, 'uly', 'marketId', defaultUnderlying)
              if currencyId is None:
                  raise ArgumentsRequired(self.id + ' fetchTickersByType requires an underlying uly or marketId parameter for options markets')
              else:
                  request['uly'] = currencyId
          response = self.publicGetMarketTickers(self.extend(request, params))
          #
          #     {
          #         "code":"0",
          #         "msg":"",
          #         "data":[
          #             {
          #                 "instType":"SPOT",
          #                 "instId":"BCD-BTC",
          #                 "last":"0.0000769",
          #                 "lastSz":"5.4788",
          #                 "askPx":"0.0000777",
          #                 "askSz":"3.2197",
          #                 "bidPx":"0.0000757",
          #                 "bidSz":"4.7509",
          #                 "open24h":"0.0000885",
          #                 "high24h":"0.0000917",
          #                 "low24h":"0.0000596",
          #                 "volCcy24h":"9.2877",
          #                 "vol24h":"124824.1985",
          #                 "ts":"1621441741434",
          #                 "sodUtc0":"0.0000905",
          #                 "sodUtc8":"0.0000729"
          #             },
          #         ]
          #     }
          #
          tickers = self.safe_value(response, 'data', [])
          return self.parse_tickers(tickers, symbols)
  
      def fetch_tickers(self, symbols=None, params={}):
          defaultType = self.safe_string_2(self.options, 'fetchTickers', 'defaultType')
          type = self.safe_string(params, 'type', defaultType)
          return self.fetch_tickers_by_type(type, symbols, self.omit(params, 'type'))
  
      def parse_trade(self, trade, market=None):
          #
          # public fetchTrades
          #
          #     {
          #         "instId":"ETH-BTC",
          #         "side":"sell",
          #         "sz":"0.119501",
          #         "px":"0.07065",
          #         "tradeId":"15826757",
          #         "ts":"1621446178316"
          #     }
          #
          # private fetchMyTrades
          #
          #     {
          #         "side":"buy",
          #         "fillSz":"0.007533",
          #         "fillPx":"2654.98",
          #         "fee":"-0.000007533",
          #         "ordId":"317321390244397056",
          #         "instType":"SPOT",
          #         "instId":"ETH-USDT",
          #         "clOrdId":"",
          #         "posSide":"net",
          #         "billId":"317321390265368576",
          #         "tag":"0",
          #         "execType":"T",
          #         "tradeId":"107601752",
          #         "feeCcy":"ETH",
          #         "ts":"1621927314985"
          #     }
          #
          id = self.safe_string(trade, 'tradeId')
          marketId = self.safe_string(trade, 'instId')
          market = self.safe_market(marketId, market, '-')
          symbol = market['symbol']
          timestamp = self.safe_integer(trade, 'ts')
          price = self.safe_string_2(trade, 'fillPx', 'px')
          amount = self.safe_string_2(trade, 'fillSz', 'sz')
          side = self.safe_string(trade, 'side')
          orderId = self.safe_string(trade, 'ordId')
          feeCostString = self.safe_string(trade, 'fee')
          fee = None
          if feeCostString is not None:
              feeCostSigned = Precise.string_neg(feeCostString)
              feeCurrencyId = self.safe_string(trade, 'feeCcy')
              feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
              fee = {
                  'cost': feeCostSigned,
                  'currency': feeCurrencyCode,
              }
          takerOrMaker = self.safe_string(trade, 'execType')
          if takerOrMaker == 'T':
              takerOrMaker = 'taker'
          elif takerOrMaker == 'M':
              takerOrMaker = 'maker'
          return self.safe_trade({
              'info': trade,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'symbol': symbol,
              'id': id,
              'order': orderId,
              'type': None,
              'takerOrMaker': takerOrMaker,
              'side': side,
              'price': price,
              'amount': amount,
              'cost': None,
              'fee': fee,
          }, market)
  
      def fetch_trades(self, symbol, since=None, limit=None, params={}):
          self.load_markets()
          market = self.market(symbol)
          request = {
              'instId': market['id'],
          }
          if limit is not None:
              request['limit'] = limit  # default 100
          response = self.publicGetMarketTrades(self.extend(request, params))
          #
          #     {
          #         "code":"0",
          #         "msg":"",
          #         "data":[
          #             {"instId":"ETH-BTC","side":"sell","sz":"0.119501","px":"0.07065","tradeId":"15826757","ts":"1621446178316"},
          #             {"instId":"ETH-BTC","side":"sell","sz":"0.03","px":"0.07068","tradeId":"15826756","ts":"1621446178066"},
          #             {"instId":"ETH-BTC","side":"buy","sz":"0.507","px":"0.07069","tradeId":"15826755","ts":"1621446175085"},
          #         ]
          #     }
          #
          data = self.safe_value(response, 'data', [])
          return self.parse_trades(data, market, since, limit)
  
      def parse_ohlcv(self, ohlcv, market=None):
          #
          #     [
          #         "1621447080000",  # timestamp
          #         "0.07073",  # open
          #         "0.07073",  # high
          #         "0.07064",  # low
          #         "0.07064",  # close
          #         "12.08863",  # base volume
          #         "0.854309"  # quote volume
          #     ]
          #
          return [
              self.safe_integer(ohlcv, 0),
              self.safe_number(ohlcv, 1),
              self.safe_number(ohlcv, 2),
              self.safe_number(ohlcv, 3),
              self.safe_number(ohlcv, 4),
              self.safe_number(ohlcv, 5),
          ]
  
      def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
          self.load_markets()
          market = self.market(symbol)
          price = self.safe_string(params, 'price')
          params = self.omit(params, 'price')
          if limit is None:
              limit = 100  # default 100, max 100
          request = {
              'instId': market['id'],
              'bar': self.timeframes[timeframe],
              'limit': limit,
          }
          defaultType = 'Candles'
          if since is not None:
              duration = self.parse_timeframe(timeframe)
              now = self.milliseconds()
              difference = now - since
              # if the since timestamp is more than limit candles back in the past
              if difference > limit * duration * 1000:
                  defaultType = 'HistoryCandles'
              durationInMilliseconds = duration * 1000
              startTime = max(since - 1, 0)
              request['before'] = startTime
              request['after'] = self.sum(startTime, durationInMilliseconds * limit)
          options = self.safe_value(self.options, 'fetchOHLCV', {})
          defaultType = self.safe_string(options, 'type', defaultType)  # Candles or HistoryCandles
          type = self.safe_string(params, 'type', defaultType)
          params = self.omit(params, 'type')
          method = 'publicGetMarket' + type
          if price == 'mark':
              method = 'publicGetMarketMarkPriceCandles'
          elif price == 'index':
              method = 'publicGetMarketIndexCandles'
          response = getattr(self, method)(self.extend(request, params))
          #
          #     {
          #         "code":"0",
          #         "msg":"",
          #         "data":[
          #             ["1621447080000","0.07073","0.07073","0.07064","0.07064","12.08863","0.854309"],
          #             ["1621447020000","0.0708","0.0709","0.0707","0.07072","58.517435","4.143309"],
          #             ["1621446960000","0.0707","0.07082","0.0707","0.07076","53.850841","3.810921"],
          #         ]
          #     }
          #
          data = self.safe_value(response, 'data', [])
          return self.parse_ohlcvs(data, market, timeframe, since, limit)
  
      def fetch_funding_rate_history(self, symbol=None, since=None, limit=None, params={}):
          if symbol is None:
              raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument')
          self.load_markets()
          market = self.market(symbol)
          request = {
              'instId': market['id'],
          }
          if since is not None:
              request['before'] = max(since - 1, 0)
          if limit is not None:
              request['limit'] = limit
          response = self.publicGetPublicFundingRateHistory(self.extend(request, params))
          #
          #     {
          #         "code":"0",
          #         "msg":"",
          #         "data":[
          #             {
          #                 "instType":"SWAP",
          #                 "instId":"BTC-USDT-SWAP",
          #                 "fundingRate":"0.018",
          #                 "realizedRate":"0.017",
          #                 "fundingTime":"1597026383085"
          #             },
          #             {
          #                 "instType":"SWAP",
          #                 "instId":"BTC-USDT-SWAP",
          #                 "fundingRate":"0.018",
          #                 "realizedRate":"0.017",
          #                 "fundingTime":"1597026383085"
          #             }
          #         ]
          #     }
          #
          rates = []
          data = self.safe_value(response, 'data')
          for i in range(0, len(data)):
              rate = data[i]
              timestamp = self.safe_number(rate, 'fundingTime')
              rates.append({
                  'symbol': self.safe_symbol(self.safe_string(rate, 'instId')),
                  'fundingRate': self.safe_number(rate, 'realizedRate'),
                  'timestamp': timestamp,
                  'datetime': self.iso8601(timestamp),
              })
          sorted = self.sort_by(rates, 'timestamp')
          return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)
  
      def fetch_index_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
          request = {
              'price': 'index',
          }
          return self.fetch_ohlcv(symbol, timeframe, since, limit, self.extend(request, params))
  
      def fetch_mark_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
          request = {
              'price': 'mark',
          }
          return self.fetch_ohlcv(symbol, timeframe, since, limit, self.extend(request, params))
  
      def parse_balance_by_type(self, type, response):
          if type == 'funding':
              return self.parse_funding_balance(response)
          else:
              return self.parse_trading_balance(response)
  
      def parse_trading_balance(self, response):
          result = {'info': response}
          data = self.safe_value(response, 'data', [])
          first = self.safe_value(data, 0, {})
          timestamp = self.safe_integer(first, 'uTime')
          details = self.safe_value(first, 'details', [])
          for i in range(0, len(details)):
              balance = details[i]
              currencyId = self.safe_string(balance, 'ccy')
              code = self.safe_currency_code(currencyId)
              account = self.account()
              # it may be incorrect to use total, free and used for swap accounts
              eq = self.safe_string(balance, 'eq')
              availEq = self.safe_string(balance, 'availEq')
              if (len(eq) < 1) or (len(availEq) < 1):
                  account['free'] = self.safe_string(balance, 'availBal')
                  account['used'] = self.safe_string(balance, 'frozenBal')
              else:
                  account['total'] = eq
                  account['free'] = availEq
              result[code] = account
          result['timestamp'] = timestamp
          result['datetime'] = self.iso8601(timestamp)
          return self.safe_balance(result)
  
      def parse_funding_balance(self, response):
          result = {'info': response}
          data = self.safe_value(response, 'data', [])
          for i in range(0, len(data)):
              balance = data[i]
              currencyId = self.safe_string(balance, 'ccy')
              code = self.safe_currency_code(currencyId)
              account = self.account()
              # it may be incorrect to use total, free and used for swap accounts
              account['total'] = self.safe_string(balance, 'bal')
              account['free'] = self.safe_string(balance, 'availBal')
              account['used'] = self.safe_string(balance, 'frozenBal')
              result[code] = account
          return self.safe_balance(result)
  
      def parse_trading_fee(self, fee, market=None):
          #
          #     {
          #         "category":"1",
          #         "delivery":"",
          #         "exercise":"",
          #         "instType":"SPOT",
          #         "level":"Lv1",
          #         "maker":"-0.0008",
          #         "taker":"-0.001",
          #         "ts":"1639043138472"
          #     }
          #
          return {
              'info': fee,
              'symbol': self.safe_symbol(None, market),
              'maker': self.safe_number(fee, 'maker'),
              'taker': self.safe_number(fee, 'taker'),
          }
  
      def fetch_trading_fee(self, symbol, params={}):
          self.load_markets()
          market = self.market(symbol)
          request = {
              'instType': market['type'].upper(),  # SPOT, MARGIN, SWAP, FUTURES, OPTION
              # 'instId': market['id'],  # only applicable to SPOT/MARGIN
              # 'uly': market['id'],  # only applicable to FUTURES/SWAP/OPTION
              # 'category': '1',  # 1 = Class A, 2 = Class B, 3 = Class C, 4 = Class D
          }
          if market['spot']:
              request['instId'] = market['id']
          elif market['swap'] or market['futures'] or market['option']:
              request['uly'] = market['baseId'] + '-' + market['quoteId']
          else:
              raise NotSupported(self.id + ' fetchTradingFee supports spot, swap, futures or option markets only')
          response = self.privateGetAccountTradeFee(self.extend(request, params))
          #
          #     {
          #         "code":"0",
          #         "data":[
          #             {
          #                 "category":"1",
          #                 "delivery":"",
          #                 "exercise":"",
          #                 "instType":"SPOT",
          #                 "level":"Lv1",
          #                 "maker":"-0.0008",
          #                 "taker":"-0.001",
          #                 "ts":"1639043138472"
          #             }
          #         ],
          #         "msg":""
          #     }
          #
          data = self.safe_value(response, 'data', [])
          first = self.safe_value(data, 0, {})
          return self.parse_trading_fee(first, market)
  
      def fetch_balance(self, params={}):
          self.load_markets()
          marketType, query = self.handle_market_type_and_params('fetchBalance', None, params)
          method = None
          if marketType == 'funding':
              method = 'privateGetAssetBalances'
          else:
              method = 'privateGetAccountBalance'
          request = {
              # 'ccy': 'BTC,ETH',  # comma-separated list of currency ids
          }
          response = getattr(self, method)(self.extend(request, query))
          #
          #     {
          #         "code":"0",
          #         "data":[
          #             {
          #                 "adjEq":"",
          #                 "details":[
          #                     {
          #                         "availBal":"",
          #                         "availEq":"28.21006347",
          #                         "cashBal":"28.21006347",
          #                         "ccy":"USDT",
          #                         "crossLiab":"",
          #                         "disEq":"28.2687404020176",
          #                         "eq":"28.21006347",
          #                         "eqUsd":"28.2687404020176",
          #                         "frozenBal":"0",
          #                         "interest":"",
          #                         "isoEq":"0",
          #                         "isoLiab":"",
          #                         "liab":"",
          #                         "maxLoan":"",
          #                         "mgnRatio":"",
          #                         "notionalLever":"0",
          #                         "ordFrozen":"0",
          #                         "twap":"0",
          #                         "uTime":"1621556539861",
          #                         "upl":"0",
          #                         "uplLiab":""
          #                     }
          #                 ],
          #                 "imr":"",
          #                 "isoEq":"0",
          #                 "mgnRatio":"",
          #                 "mmr":"",
          #                 "notionalUsd":"",
          #                 "ordFroz":"",
          #                 "totalEq":"28.2687404020176",
          #                 "uTime":"1621556553510"
          #             }
          #         ],
          #         "msg":""
          #     }
          #
          #     {
          #         "code":"0",
          #         "data":[
          #             {
          #                 "adjEq":"",
          #                 "details":[
          #                     {
          #                         "availBal":"0.049",
          #                         "availEq":"",
          #                         "cashBal":"0.049",
          #                         "ccy":"BTC",
          #                         "crossLiab":"",
          #                         "disEq":"1918.55678",
          #                         "eq":"0.049",
          #                         "eqUsd":"1918.55678",
          #                         "frozenBal":"0",
          #                         "interest":"",
          #                         "isoEq":"",
          #                         "isoLiab":"",
          #                         "liab":"",
          #                         "maxLoan":"",
          #                         "mgnRatio":"",
          #                         "notionalLever":"",
          #                         "ordFrozen":"0",
          #                         "twap":"0",
          #                         "uTime":"1621973128591",
          #                         "upl":"",
          #                         "uplLiab":""
          #                     }
          #                 ],
          #                 "imr":"",
          #                 "isoEq":"",
          #                 "mgnRatio":"",
          #                 "mmr":"",
          #                 "notionalUsd":"",
          #                 "ordFroz":"",
          #                 "totalEq":"1918.55678",
          #                 "uTime":"1622045126908"
          #             }
          #         ],
          #         "msg":""
          #     }
          #
          # funding
          #
          #     {
          #         "code":"0",
          #         "data":[
          #             {
          #                 "availBal":"0.00005426",
          #                 "bal":0.0000542600000000,
          #                 "ccy":"BTC",
          #                 "frozenBal":"0"
          #             }
          #         ],
          #         "msg":""
          #     }
          #
          return self.parse_balance_by_type(marketType, response)
  
      def create_order(self, symbol, type, side, amount, price=None, params={}):
          self.load_markets()
          market = self.market(symbol)
          request = {
              'instId': market['id'],
              #
              #     Simple:
              #     - SPOT and OPTION buyer: cash
              #
              #     Single-currency margin:
              #     - Isolated MARGIN: isolated
              #     - Cross MARGIN: cross
              #     - Cross SPOT: cash
              #     - Cross FUTURES/SWAP/OPTION: cross
              #     - Isolated FUTURES/SWAP/OPTION: isolated
              #
              #     Multi-currency margin:
              #     - Isolated MARGIN: isolated
              #     - Cross SPOT: cross
              #     - Cross FUTURES/SWAP/OPTION: cross
              #     - Isolated FUTURES/SWAP/OPTION: isolated
              #
              # 'ccy': currency['id'],  # only applicable to cross MARGIN orders in single-currency margin
              # 'clOrdId': clientOrderId,  # up to 32 characters, must be unique
              'tag': BROKER_ID,  # up to 8 characters
              #
              #     In long/short mode, side and posSide need to be combined
              #
              #     buy with long means open long
              #     sell with long means close long
              #     sell with short means open short
              #     buy with short means close short
              #
              'side': side,
              # 'posSide': 'long',  # long, short,  # required in the long/short mode, and can only be long or short
              'ordType': type,  # market, limit, post_only, fok, ioc
              #
              #     for SPOT/MARGIN bought and sold at a limit price, sz refers to the amount of trading currency
              #     for SPOT/MARGIN bought at a market price, sz refers to the amount of quoted currency
              #     for SPOT/MARGIN sold at a market price, sz refers to the amount of trading currency
              #     for FUTURES/SWAP/OPTION buying and selling, sz refers to the number of contracts
              #
              # 'sz': self.amount_to_precision(symbol, amount),
              # 'px': self.price_to_precision(symbol, price),  # limit orders only
              # 'reduceOnly': False,  # MARGIN orders only
          }
          tdMode = self.safe_string_lower(params, 'tdMode')
          if market['spot']:
              request['tdMode'] = 'cash'
          elif market['contract']:
              if tdMode is None:
                  raise ArgumentsRequired(self.id + ' params["tdMode"] is required to be either "isolated" or "cross"')
              elif (tdMode != 'isolated') and (tdMode != 'cross'):
                  raise BadRequest(self.id + ' params["tdMode"] must be either "isolated" or "cross"')
          postOnly = self.safe_value(params, 'postOnly', False)
          if postOnly:
              request['ordType'] = 'post_only'
              params = self.omit(params, ['postOnly'])
          clientOrderId = self.safe_string_2(params, 'clOrdId', 'clientOrderId')
          if clientOrderId is None:
              brokerId = self.safe_string(self.options, 'brokerId')
              if brokerId is not None:
                  request['clOrdId'] = brokerId + self.uuid16()
          else:
              request['clOrdId'] = clientOrderId
              params = self.omit(params, ['clOrdId', 'clientOrderId'])
          request['sz'] = self.amount_to_precision(symbol, amount)
          if type == 'market':
              if market['type'] == 'spot' and side == 'buy':
                  # spot market buy: "sz" can refer either to base currency units or to quote currency units
                  # see documentation: https://www.okex.com/docs-v5/en/#rest-api-trade-place-order
                  defaultTgtCcy = self.safe_string(self.options, 'tgtCcy', 'base_ccy')
                  tgtCcy = self.safe_string(params, 'tgtCcy', defaultTgtCcy)
                  if tgtCcy == 'quote_ccy':
                      # quote_ccy: sz refers to units of quote currency
                      request['tgtCcy'] = 'quote_ccy'
                      notional = self.safe_number(params, 'sz')
                      createMarketBuyOrderRequiresPrice = self.safe_value(self.options, 'createMarketBuyOrderRequiresPrice', True)
                      if createMarketBuyOrderRequiresPrice:
                          if price is not None:
                              if notional is None:
                                  notional = amount * price
                          elif notional is None:
                              raise InvalidOrder(self.id + " createOrder() requires the price argument with market buy orders to calculate total order cost(amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = False and supply the total cost value in the 'amount' argument or in the 'sz' extra parameter(the exchange-specific behaviour)")
                      else:
                          notional = amount if (notional is None) else notional
                      precision = market['precision']['price']
                      request['sz'] = self.decimal_to_precision(notional, TRUNCATE, precision, self.precisionMode)
                  else:
                      # base_ccy: sz refers to units of base currency
                      request['tgtCcy'] = 'base_ccy'
                  params = self.omit(params, ['tgtCcy'])
          else:
              # non-market orders
              request['px'] = self.price_to_precision(symbol, price)
          extendedRequest = None
          defaultMethod = self.safe_string(self.options, 'createOrder', 'privatePostTradeBatchOrders')  # or privatePostTradeOrder
          if defaultMethod == 'privatePostTradeOrder':
              extendedRequest = self.extend(request, params)
          elif defaultMethod == 'privatePostTradeBatchOrders':
              # keep the request body the same
              # submit a single order in an array to the batch order endpoint
              # because it has a lower ratelimit
              extendedRequest = [self.extend(request, params)]
          else:
              raise ExchangeError(self.id + ' self.options["createOrder"] must be either privatePostTradeBatchOrders or privatePostTradeOrder')
          response = getattr(self, defaultMethod)(extendedRequest)
          #
          #     {
          #         "code": "0",
          #         "msg": "",
          #         "data": [
          #             {
          #                 "clOrdId": "oktswap6",
          #                 "ordId": "312269865356374016",
          #                 "tag": "",
          #                 "sCode": "0",
          #                 "sMsg": ""
          #             }
          #         ]
          #     }
          #
          data = self.safe_value(response, 'data', [])
          first = self.safe_value(data, 0)
          order = self.parse_order(first, market)
          return self.extend(order, {
              'type': type,
              'side': side,
          })
  
      def cancel_order(self, id, symbol=None, params={}):
          if symbol is None:
              raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
          self.load_markets()
          market = self.market(symbol)
          request = {
              'instId': market['id'],
              # 'ordId': id,  # either ordId or clOrdId is required
              # 'clOrdId': clientOrderId,
          }
          clientOrderId = self.safe_string_2(params, 'clOrdId', 'clientOrderId')
          if clientOrderId is not None:
              request['clOrdId'] = clientOrderId
          else:
              request['ordId'] = id
          query = self.omit(params, ['clOrdId', 'clientOrderId'])
          response = self.privatePostTradeCancelOrder(self.extend(request, query))
          # {"code":"0","data":[{"clOrdId":"","ordId":"317251910906576896","sCode":"0","sMsg":""}],"msg":""}
          data = self.safe_value(response, 'data', [])
          order = self.safe_value(data, 0)
          return self.parse_order(order, market)
  
      def cancel_orders(self, ids, symbol=None, params={}):
          if symbol is None:
              raise ArgumentsRequired(self.id + ' canelOrders() requires a symbol argument')
          self.load_markets()
          market = self.market(symbol)
          request = []
          clientOrderId = self.safe_value_2(params, 'clOrdId', 'clientOrderId')
          if clientOrderId is None:
              if isinstance(ids, basestring):
                  orderIds = ids.split(',')
                  for i in range(0, len(orderIds)):
                      request.append({
                          'instId': market['id'],
                          'ordId': orderIds[i],
                      })
              else:
                  for i in range(0, len(ids)):
                      request.append({
                          'instId': market['id'],
                          'ordId': ids[i],
                      })
          elif isinstance(clientOrderId, list):
              for i in range(0, len(clientOrderId)):
                  request.append({
                      'instId': market['id'],
                      'clOrdId': clientOrderId[i],
                  })
          elif isinstance(clientOrderId, basestring):
              request.append({
                  'instId': market['id'],
                  'clOrdId': clientOrderId,
              })
          response = 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_value(response, 'data', [])
          return self.parse_orders(ordersData, market, None, None, params)
  
      def parse_order_status(self, status):
          statuses = {
              'canceled': 'canceled',
              'live': 'open',
              'partially_filled': 'open',
              'filled': 'closed',
          }
          return self.safe_string(statuses, status, status)
  
      def parse_order(self, order, market=None):
          #
          # createOrder
          #
          #     {
          #         "clOrdId": "oktswap6",
          #         "ordId": "312269865356374016",
          #         "tag": "",
          #         "sCode": "0",
          #         "sMsg": ""
          #     }
          #
          # fetchOrder, fetchOpenOrders
          #
          #     {
          #         "accFillSz":"0",
          #         "avgPx":"",
          #         "cTime":"1621910749815",
          #         "category":"normal",
          #         "ccy":"",
          #         "clOrdId":"",
          #         "fee":"0",
          #         "feeCcy":"ETH",
          #         "fillPx":"",
          #         "fillSz":"0",
          #         "fillTime":"",
          #         "instId":"ETH-USDT",
          #         "instType":"SPOT",
          #         "lever":"",
          #         "ordId":"317251910906576896",
          #         "ordType":"limit",
          #         "pnl":"0",
          #         "posSide":"net",
          #         "px":"2000",
          #         "rebate":"0",
          #         "rebateCcy":"USDT",
          #         "side":"buy",
          #         "slOrdPx":"",
          #         "slTriggerPx":"",
          #         "state":"live",
          #         "sz":"0.001",
          #         "tag":"",
          #         "tdMode":"cash",
          #         "tpOrdPx":"",
          #         "tpTriggerPx":"",
          #         "tradeId":"",
          #         "uTime":"1621910749815"
          #     }
          #
          id = self.safe_string(order, 'ordId')
          timestamp = self.safe_integer(order, 'cTime')
          lastTradeTimestamp = self.safe_integer(order, 'fillTime')
          side = self.safe_string(order, 'side')
          type = self.safe_string(order, 'ordType')
          postOnly = None
          timeInForce = None
          if type == 'post_only':
              postOnly = True
              type = 'limit'
          elif type == 'fok':
              timeInForce = 'FOK'
              type = 'limit'
          elif type == 'ioc':
              timeInForce = 'IOC'
              type = 'limit'
          marketId = self.safe_string(order, 'instId')
          symbol = self.safe_symbol(marketId, market, '-')
          filled = self.safe_string(order, 'accFillSz')
          price = self.safe_string_2(order, 'px', 'slOrdPx')
          average = self.safe_string(order, 'avgPx')
          status = self.parse_order_status(self.safe_string(order, 'state'))
          feeCostString = self.safe_string(order, 'fee')
          amount = None
          cost = None
          # spot market buy: "sz" can refer either to base currency units or to quote currency units
          # see documentation: https://www.okex.com/docs-v5/en/#rest-api-trade-place-order
          defaultTgtCcy = self.safe_string(self.options, 'tgtCcy', 'base_ccy')
          tgtCcy = self.safe_string(order, 'tgtCcy', defaultTgtCcy)
          instType = self.safe_string(order, 'instType')
          if (side == 'buy') and (type == 'market') and (instType == 'SPOT') and (tgtCcy == 'quote_ccy'):
              # "sz" refers to the cost
              cost = self.safe_string(order, 'sz')
          else:
              # "sz" refers to the trade currency amount
              amount = self.safe_string(order, 'sz')
          fee = None
          if feeCostString is not None:
              feeCostSigned = Precise.string_neg(feeCostString)
              feeCurrencyId = self.safe_string(order, 'feeCcy')
              feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
              fee = {
                  'cost': self.parse_number(feeCostSigned),
                  'currency': feeCurrencyCode,
              }
          clientOrderId = self.safe_string(order, 'clOrdId')
          if (clientOrderId is not None) and (len(clientOrderId) < 1):
              clientOrderId = None  # fix empty clientOrderId string
          stopPrice = self.safe_number(order, 'slTriggerPx')
          return self.safe_order({
              'info': order,
              'id': id,
              'clientOrderId': clientOrderId,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'lastTradeTimestamp': lastTradeTimestamp,
              'symbol': symbol,
              'type': type,
              'timeInForce': timeInForce,
              'postOnly': postOnly,
              'side': side,
              'price': price,
              'stopPrice': stopPrice,
              'average': average,
              'cost': cost,
              'amount': amount,
              'filled': filled,
              'remaining': None,
              'status': status,
              'fee': fee,
              'trades': None,
          }, market)
  
      def fetch_order(self, id, symbol=None, params={}):
          if symbol is None:
              raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
          self.load_markets()
          market = self.market(symbol)
          request = {
              'instId': market['id'],
              # 'clOrdId': 'abcdef12345',  # optional, [a-z0-9]{1,32}
              # 'ordId': id,
          }
          clientOrderId = self.safe_string_2(params, 'clOrdId', 'clientOrderId')
          if clientOrderId is not None:
              request['clOrdId'] = clientOrderId
          else:
              request['ordId'] = id
          query = self.omit(params, ['clOrdId', 'clientOrderId'])
          response = self.privateGetTradeOrder(self.extend(request, query))
          #
          #     {
          #         "code":"0",
          #         "data":[
          #             {
          #                 "accFillSz":"0",
          #                 "avgPx":"",
          #                 "cTime":"1621910749815",
          #                 "category":"normal",
          #                 "ccy":"",
          #                 "clOrdId":"",
          #                 "fee":"0",
          #                 "feeCcy":"ETH",
          #                 "fillPx":"",
          #                 "fillSz":"0",
          #                 "fillTime":"",
          #                 "instId":"ETH-USDT",
          #                 "instType":"SPOT",
          #                 "lever":"",
          #                 "ordId":"317251910906576896",
          #                 "ordType":"limit",
          #                 "pnl":"0",
          #                 "posSide":"net",
          #                 "px":"2000",
          #                 "rebate":"0",
          #                 "rebateCcy":"USDT",
          #                 "side":"buy",
          #                 "slOrdPx":"",
          #                 "slTriggerPx":"",
          #                 "state":"live",
          #                 "sz":"0.001",
          #                 "tag":"",
          #                 "tdMode":"cash",
          #                 "tpOrdPx":"",
          #                 "tpTriggerPx":"",
          #                 "tradeId":"",
          #                 "uTime":"1621910749815"
          #             }
          #         ],
          #         "msg":""
          #     }
          #
          data = self.safe_value(response, 'data', [])
          order = self.safe_value(data, 0)
          return self.parse_order(order, market)
  
      def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
          self.load_markets()
          request = {
              # 'instType': 'SPOT',  # SPOT, MARGIN, SWAP, FUTURES, OPTION
              # 'uly': currency['id'],
              # 'instId': market['id'],
              # 'ordType': 'limit',  # market, limit, post_only, fok, ioc, comma-separated
              # 'state': 'live',  # live, partially_filled
              # 'after': orderId,
              # 'before': orderId,
              # 'limit': limit,  # default 100, max 100
          }
          market = None
          if symbol is not None:
              market = self.market(symbol)
              request['instId'] = market['id']
          if limit is not None:
              request['limit'] = limit  # default 100, max 100
          response = self.privateGetTradeOrdersPending(self.extend(request, params))
          #
          #     {
          #         "code":"0",
          #         "data":[
          #             {
          #                 "accFillSz":"0",
          #                 "avgPx":"",
          #                 "cTime":"1621910749815",
          #                 "category":"normal",
          #                 "ccy":"",
          #                 "clOrdId":"",
          #                 "fee":"0",
          #                 "feeCcy":"ETH",
          #                 "fillPx":"",
          #                 "fillSz":"0",
          #                 "fillTime":"",
          #                 "instId":"ETH-USDT",
          #                 "instType":"SPOT",
          #                 "lever":"",
          #                 "ordId":"317251910906576896",
          #                 "ordType":"limit",
          #                 "pnl":"0",
          #                 "posSide":"net",
          #                 "px":"2000",
          #                 "rebate":"0",
          #                 "rebateCcy":"USDT",
          #                 "side":"buy",
          #                 "slOrdPx":"",
          #                 "slTriggerPx":"",
          #                 "state":"live",
          #                 "sz":"0.001",
          #                 "tag":"",
          #                 "tdMode":"cash",
          #                 "tpOrdPx":"",
          #                 "tpTriggerPx":"",
          #                 "tradeId":"",
          #                 "uTime":"1621910749815"
          #             }
          #         ],
          #         "msg":""
          #     }
          #
          data = self.safe_value(response, 'data', [])
          return self.parse_orders(data, market, since, limit)
  
      def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
          self.load_markets()
          defaultType = self.safe_string(self.options, 'defaultType')
          options = self.safe_value(self.options, 'fetchClosedOrders', {})
          type = self.safe_string(options, 'type', defaultType)
          type = self.safe_string(params, 'type', type)
          params = self.omit(params, 'type')
          request = {
              # 'instType': type.upper(),  # SPOT, MARGIN, SWAP, FUTURES, OPTION
              # 'uly': currency['id'],
              # 'instId': market['id'],
              # 'ordType': 'limit',  # market, limit, post_only, fok, ioc, comma-separated
              # 'state': 'filled',  # filled, canceled
              # 'after': orderId,
              # 'before': orderId,
              # 'limit': limit,  # default 100, max 100
          }
          market = None
          if symbol is not None:
              market = self.market(symbol)
              if market['futures'] or market['swap']:
                  type = market['type']
              request['instId'] = market['id']
          request['instType'] = type.upper()
          if limit is not None:
              request['limit'] = limit  # default 100, max 100
          method = self.safe_string(options, 'method', 'privateGetTradeOrdersHistory')
          response = getattr(self, method)(self.extend(request, params))
          #
          #     {
          #         "code":"0",
          #         "data":[
          #             {
          #                 "accFillSz":"0",
          #                 "avgPx":"",
          #                 "cTime":"1621910749815",
          #                 "category":"normal",
          #                 "ccy":"",
          #                 "clOrdId":"",
          #                 "fee":"0",
          #                 "feeCcy":"ETH",
          #                 "fillPx":"",
          #                 "fillSz":"0",
          #                 "fillTime":"",
          #                 "instId":"ETH-USDT",
          #                 "instType":"SPOT",
          #                 "lever":"",
          #                 "ordId":"317251910906576896",
          #                 "ordType":"limit",
          #                 "pnl":"0",
          #                 "posSide":"net",
          #                 "px":"2000",
          #                 "rebate":"0",
          #                 "rebateCcy":"USDT",
          #                 "side":"buy",
          #                 "slOrdPx":"",
          #                 "slTriggerPx":"",
          #                 "state":"live",
          #                 "sz":"0.001",
          #                 "tag":"",
          #                 "tdMode":"cash",
          #                 "tpOrdPx":"",
          #                 "tpTriggerPx":"",
          #                 "tradeId":"",
          #                 "uTime":"1621910749815"
          #             }
          #         ],
          #         "msg":""
          #     }
          #
          data = self.safe_value(response, 'data', [])
          return self.parse_orders(data, market, since, limit)
  
      def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
          defaultType = self.safe_string(self.options, 'defaultType')
          options = self.safe_value(self.options, 'fetchMyTrades', {})
          type = self.safe_string(options, 'type', defaultType)
          params = self.omit(params, 'type')
          self.load_markets()
          request = {
              # 'instType': 'SPOT',  # SPOT, MARGIN, SWAP, FUTURES, OPTION
              # 'uly': currency['id'],
              # 'instId': market['id'],
              # 'ordId': orderId,
              # 'after': billId,
              # 'before': billId,
              # 'limit': limit,  # default 100, max 100
          }
          market = None
          if symbol is not None:
              market = self.market(symbol)
              request['instId'] = market['id']
              type = market['type']
          request['instType'] = type.upper()
          if limit is not None:
              request['limit'] = limit  # default 100, max 100
          response = self.privateGetTradeFillsHistory(self.extend(request, params))
          #
          #     {
          #         "code":"0",
          #         "data":[
          #             {
          #                 "side":"buy",
          #                 "fillSz":"0.007533",
          #                 "fillPx":"2654.98",
          #                 "fee":"-0.000007533",
          #                 "ordId":"317321390244397056",
          #                 "instType":"SPOT",
          #                 "instId":"ETH-USDT",
          #                 "clOrdId":"",
          #                 "posSide":"net",
          #                 "billId":"317321390265368576",
          #                 "tag":"0",
          #                 "execType":"T",
          #                 "tradeId":"107601752",
          #                 "feeCcy":"ETH",
          #                 "ts":"1621927314985"
          #             }
          #         ],
          #         "msg":""
          #     }
          #
          data = self.safe_value(response, 'data', [])
          return self.parse_trades(data, market, since, limit, params)
  
      def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
          request = {
              # 'instrument_id': market['id'],
              'ordId': id,
              # 'after': '1',  # return the page after the specified page number
              # 'before': '1',  # return the page before the specified page number
              # 'limit': limit,  # optional, number of results per request, default = maximum = 100
          }
          return self.fetch_my_trades(symbol, since, limit, self.extend(request, params))
  
      def fetch_ledger(self, code=None, since=None, limit=None, params={}):
          self.load_markets()
          options = self.safe_value(self.options, 'fetchLedger', {})
          method = self.safe_string(options, 'method')
          method = self.safe_string(params, 'method', method)
          params = self.omit(params, 'method')
          request = {
              # 'instType': None,  # 'SPOT', 'MARGIN', 'SWAP', 'FUTURES", 'OPTION'
              # 'ccy': None,  # currency['id'],
              # 'mgnMode': None,  # 'isolated', 'cross'
              # 'ctType': None,  # 'linear', 'inverse', only applicable to FUTURES/SWAP
              # 'type': None,
              #     1 Transfer,
              #     2 Trade,
              #     3 Delivery,
              #     4 Auto token conversion,
              #     5 Liquidation,
              #     6 Margin transfer,
              #     7 Interest deduction,
              #     8 Funding rate,
              #     9 ADL,
              #     10 Clawback,
              #     11 System token conversion
              # 'subType': None,
              #     1 Buy
              #     2 Sell
              #     3 Open long
              #     4 Open short
              #     5 Close long
              #     6 Close short
              #     9 Interest deduction
              #     11 Transfer in
              #     12 Transfer out
              #     160 Manual margin increase
              #     161 Manual margin decrease
              #     162 Auto margin increase
              #     110 Auto buy
              #     111 Auto sell
              #     118 System token conversion transfer in
              #     119 System token conversion transfer out
              #     100 Partial liquidation close long
              #     101 Partial liquidation close short
              #     102 Partial liquidation buy
              #     103 Partial liquidation sell
              #     104 Liquidation long
              #     105 Liquidation short
              #     106 Liquidation buy
              #     107 Liquidation sell
              #     110 Liquidation transfer in
              #     111 Liquidation transfer out
              #     125 ADL close long
              #     126 ADL close short
              #     127 ADL buy
              #     128 ADL sell
              #     170 Exercised
              #     171 Counterparty exercised
              #     172 Expired OTM
              #     112 Delivery long
              #     113 Delivery short
              #     117 Delivery/Exercise clawback
              #     173 Funding fee expense
              #     174 Funding fee income
              #
              # 'after': 'id',  # return records earlier than the requested bill id
              # 'before': 'id',  # return records newer than the requested bill id
              # 'limit': 100,  # default 100, max 100
          }
          if limit is not None:
              request['limit'] = limit
          currency = None
          if code is not None:
              currency = self.currency(code)
              request['ccy'] = currency['id']
          response = getattr(self, method)(self.extend(request, params))
          #
          # privateGetAccountBills, privateGetAccountBillsArchive
          #
          #     {
          #         "code": "0",
          #         "msg": "",
          #         "data": [
          #             {
          #                 "bal": "0.0000819307998198",
          #                 "balChg": "-664.2679586599999802",
          #                 "billId": "310394313544966151",
          #                 "ccy": "USDT",
          #                 "fee": "0",
          #                 "from": "",
          #                 "instId": "LTC-USDT",
          #                 "instType": "SPOT",
          #                 "mgnMode": "cross",
          #                 "notes": "",
          #                 "ordId": "310394313519800320",
          #                 "pnl": "0",
          #                 "posBal": "0",
          #                 "posBalChg": "0",
          #                 "subType": "2",
          #                 "sz": "664.26795866",
          #                 "to": "",
          #                 "ts": "1620275771196",
          #                 "type": "2"
          #             }
          #         ]
          #     }
          #
          # privateGetAssetBills
          #
          #     {
          #         "code": "0",
          #         "msg": "",
          #         "data": [
          #             {
          #                 "billId": "12344",
          #                 "ccy": "BTC",
          #                 "balChg": "2",
          #                 "bal": "12",
          #                 "type": "1",
          #                 "ts": "1597026383085"
          #             }
          #         ]
          #     }
          #
          data = self.safe_value(response, 'data', [])
          return self.parse_ledger(data, currency, since, limit)
  
      def parse_ledger_entry_type(self, type):
          types = {
              '1': 'transfer',  # transfer
              '2': 'trade',  # trade
              '3': 'trade',  # delivery
              '4': 'rebate',  # auto token conversion
              '5': 'trade',  # liquidation
              '6': 'transfer',  # margin transfer
              '7': 'trade',  # interest deduction
              '8': 'fee',  # funding rate
              '9': 'trade',  # adl
              '10': 'trade',  # clawback
              '11': 'trade',  # system token conversion
          }
          return self.safe_string(types, type, type)
  
      def parse_ledger_entry(self, item, currency=None):
          #
          # privateGetAccountBills, privateGetAccountBillsArchive
          #
          #     {
          #         "bal": "0.0000819307998198",
          #         "balChg": "-664.2679586599999802",
          #         "billId": "310394313544966151",
          #         "ccy": "USDT",
          #         "fee": "0",
          #         "from": "",
          #         "instId": "LTC-USDT",
          #         "instType": "SPOT",
          #         "mgnMode": "cross",
          #         "notes": "",
          #         "ordId": "310394313519800320",
          #         "pnl": "0",
          #         "posBal": "0",
          #         "posBalChg": "0",
          #         "subType": "2",
          #         "sz": "664.26795866",
          #         "to": "",
          #         "ts": "1620275771196",
          #         "type": "2"
          #     }
          #
          # privateGetAssetBills
          #
          #     {
          #         "billId": "12344",
          #         "ccy": "BTC",
          #         "balChg": "2",
          #         "bal": "12",
          #         "type": "1",
          #         "ts": "1597026383085"
          #     }
          #
          id = self.safe_string(item, 'billId')
          account = None
          referenceId = self.safe_string(item, 'ordId')
          referenceAccount = None
          type = self.parse_ledger_entry_type(self.safe_string(item, 'type'))
          code = self.safe_currency_code(self.safe_string(item, 'ccy'), currency)
          amountString = self.safe_string(item, 'balChg')
          amount = self.parse_number(amountString)
          timestamp = self.safe_integer(item, 'ts')
          feeCostString = self.safe_string(item, 'fee')
          fee = None
          if feeCostString is not None:
              fee = {
                  'cost': self.parse_number(Precise.string_neg(feeCostString)),
                  'currency': code,
              }
          before = None
          afterString = self.safe_string(item, 'bal')
          after = self.parse_number(afterString)
          status = 'ok'
          marketId = self.safe_string(item, 'instId')
          symbol = None
          if marketId in self.markets_by_id:
              market = self.markets_by_id[marketId]
              symbol = market['symbol']
          return {
              'id': id,
              'info': item,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'account': account,
              'referenceId': referenceId,
              'referenceAccount': referenceAccount,
              'type': type,
              'currency': code,
              'symbol': symbol,
              'amount': amount,
              'before': before,  # balance before
              'after': after,  # balance after
              'status': status,
              'fee': fee,
          }
  
      def parse_deposit_address(self, depositAddress, currency=None):
          #
          #     {
          #         "addr": "okbtothemoon",
          #         "memo": "971668",  # may be missing
          #         "tag":"52055",  # may be missing
          #         "pmtId": "",  # may be missing
          #         "ccy": "BTC",
          #         "to": "6",  # 1 SPOT, 3 FUTURES, 6 FUNDING, 9 SWAP, 12 OPTION, 18 Unified account
          #         "selected": True
          #     }
          #
          #     {
          #         "ccy":"usdt-erc20",
          #         "to":"6",
          #         "addr":"0x696abb81974a8793352cbd33aadcf78eda3cfdfa",
          #         "selected":true
          #     }
          #
          #     {
          #       "chain": "ETH-OKExChain",
          #       "ctAddr": "72315c",
          #       "ccy": "ETH",
          #       "to": "6",
          #       "addr": "0x1c9f2244d1ccaa060bd536827c18925db10db102",
          #       "selected": True
          #     }
          #
          address = self.safe_string(depositAddress, 'addr')
          tag = self.safe_string_2(depositAddress, 'tag', 'pmtId')
          tag = self.safe_string(depositAddress, 'memo', tag)
          currencyId = self.safe_string(depositAddress, 'ccy')
          currency = self.safe_currency(currencyId, currency)
          code = currency['code']
          chain = self.safe_string(depositAddress, 'chain')
          networks = self.safe_value(currency, 'networks', {})
          networksById = self.index_by(networks, 'id')
          networkData = self.safe_value(networksById, chain)
          network = self.safe_string(networkData, 'network')
          self.check_address(address)
          return {
              'currency': code,
              'address': address,
              'tag': tag,
              'network': network,
              'info': depositAddress,
          }
  
      def fetch_deposit_addresses_by_network(self, code, params={}):
          self.load_markets()
          currency = self.currency(code)
          request = {
              'ccy': currency['id'],
          }
          response = self.privateGetAssetDepositAddress(self.extend(request, params))
          #
          #     {
          #         "code": "0",
          #         "msg": "",
          #         "data": [
          #             {
          #                 "addr": "okbtothemoon",
          #                 "memo": "971668",  # may be missing
          #                 "tag":"52055",  # may be missing
          #                 "pmtId": "",  # may be missing
          #                 "ccy": "BTC",
          #                 "to": "6",  # 1 SPOT, 3 FUTURES, 6 FUNDING, 9 SWAP, 12 OPTION, 18 Unified account
          #                 "selected": True
          #             },
          #             # {"ccy":"usdt-erc20","to":"6","addr":"0x696abb81974a8793352cbd33aadcf78eda3cfdfa","selected":true},
          #             # {"ccy":"usdt-trc20","to":"6","addr":"TRrd5SiSZrfQVRKm4e9SRSbn2LNTYqCjqx","selected":true},
          #             # {"ccy":"usdt_okexchain","to":"6","addr":"0x696abb81974a8793352cbd33aadcf78eda3cfdfa","selected":true},
          #             # {"ccy":"usdt_kip20","to":"6","addr":"0x696abb81974a8793352cbd33aadcf78eda3cfdfa","selected":true},
          #         ]
          #     }
          #
          data = self.safe_value(response, 'data', [])
          filtered = self.filter_by(data, 'selected', True)
          parsed = self.parse_deposit_addresses(filtered, [code], False)
          return self.index_by(parsed, 'network')
  
      def fetch_deposit_address(self, code, params={}):
          rawNetwork = self.safe_string_upper(params, 'network')
          networks = self.safe_value(self.options, 'networks', {})
          network = self.safe_string(networks, rawNetwork, rawNetwork)
          params = self.omit(params, 'network')
          response = self.fetch_deposit_addresses_by_network(code, params)
          result = None
          if network is None:
              result = self.safe_value(response, code)
              if result is None:
                  alias = self.safe_string(networks, code, code)
                  result = self.safe_value(response, alias)
                  if result is None:
                      defaultNetwork = self.safe_string(self.options, 'defaultNetwork', 'ERC20')
                      result = self.safe_value(response, defaultNetwork)
                      if result is None:
                          values = list(response.values())
                          result = self.safe_value(values, 0)
                          if result is None:
                              raise InvalidAddress(self.id + ' fetchDepositAddress() cannot find deposit address for ' + code)
              return result
          result = self.safe_value(response, network)
          if result is None:
              raise InvalidAddress(self.id + ' fetchDepositAddress() cannot find ' + network + ' deposit address for ' + code)
          return result
  
      def withdraw(self, code, amount, address, tag=None, params={}):
          tag, params = self.handle_withdraw_tag_and_params(tag, params)
          self.check_address(address)
          self.load_markets()
          currency = self.currency(code)
          if tag is not None:
              address = address + ':' + tag
          fee = self.safe_string(params, 'fee')
          if fee is None:
              raise ArgumentsRequired(self.id + " withdraw() requires a 'fee' string parameter, network transaction fee must be ≥ 0. Withdrawals to OKCoin or OKEx are fee-free, please set '0'. Withdrawing to external digital asset address requires network transaction fee.")
          request = {
              'ccy': currency['id'],
              'toAddr': address,
              'dest': '4',  # 2 = OKCoin International, 3 = OKEx 4 = others
              'amt': self.number_to_string(amount),
              'fee': self.number_to_string(fee),  # withdrawals to OKCoin or OKEx are fee-free, please set 0
          }
          if 'password' in params:
              request['pwd'] = params['password']
          elif 'pwd' in params:
              request['pwd'] = params['pwd']
          networks = self.safe_value(self.options, 'networks', {})
          network = self.safe_string_upper(params, 'network')  # self line allows the user to specify either ERC20 or ETH
          network = self.safe_string(networks, network, network)  # handle ETH>ERC20 alias
          if network is not None:
              request['chain'] = currency['id'] + '-' + network
              params = self.omit(params, 'network')
          query = self.omit(params, ['fee', 'password', 'pwd'])
          if not ('pwd' in request):
              raise ExchangeError(self.id + ' withdraw() requires a password parameter or a pwd parameter, it must be the funding password, not the API passphrase')
          response = self.privatePostAssetWithdrawal(self.extend(request, query))
          #
          #     {
          #         "code": "0",
          #         "msg": "",
          #         "data": [
          #             {
          #                 "amt": "0.1",
          #                 "wdId": "67485",
          #                 "ccy": "BTC"
          #             }
          #         ]
          #     }
          #
          data = self.safe_value(response, 'data', [])
          transaction = self.safe_value(data, 0)
          return self.parse_transaction(transaction, currency)
  
      def fetch_deposits(self, code=None, since=None, limit=None, params={}):
          self.load_markets()
          request = {
              # 'ccy': currency['id'],
              # 'state': 2,  # 0 waiting for confirmation, 1 deposit credited, 2 deposit successful
              # 'after': since,
              # 'before' self.milliseconds(),
              # 'limit': limit,  # default 100, max 100
          }
          currency = None
          if code is not None:
              currency = self.currency(code)
              request['ccy'] = currency['id']
          if since is not None:
              request['before'] = max(since - 1, 0)
          if limit is not None:
              request['limit'] = limit  # default 100, max 100
          response = self.privateGetAssetDepositHistory(self.extend(request, params))
          #
          #     {
          #         "code": "0",
          #         "msg": "",
          #         "data": [
          #             {
          #                 "amt": "0.01044408",
          #                 "txId": "1915737_3_0_0_asset",
          #                 "ccy": "BTC",
          #                 "from": "13801825426",
          #                 "to": "",
          #                 "ts": "1597026383085",
          #                 "state": "2",
          #                 "depId": "4703879"
          #             },
          #             {
          #                 "amt": "491.6784211",
          #                 "txId": "1744594_3_184_0_asset",
          #                 "ccy": "OKB",
          #                 "from": "",
          #                 "to": "",
          #                 "ts": "1597026383085",
          #                 "state": "2",
          #                 "depId": "4703809"
          #             },
          #             {
          #                 "amt": "223.18782496",
          #                 "txId": "6d892c669225b1092c780bf0da0c6f912fc7dc8f6b8cc53b003288624c",
          #                 "ccy": "USDT",
          #                 "from": "",
          #                 "to": "39kK4XvgEuM7rX9frgyHoZkWqx4iKu1spD",
          #                 "ts": "1597026383085",
          #                 "state": "2",
          #                 "depId": "4703779"
          #             }
          #         ]
          #     }
          #
          data = self.safe_value(response, 'data', [])
          return self.parse_transactions(data, currency, since, limit, params)
  
      def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
          self.load_markets()
          request = {
              # 'ccy': currency['id'],
              # 'state': 2,  # -3: pending cancel, -2 canceled, -1 failed, 0, pending, 1 sending, 2 sent, 3 awaiting email verification, 4 awaiting manual verification, 5 awaiting identity verification
              # 'after': since,
              # 'before': self.milliseconds(),
              # 'limit': limit,  # default 100, max 100
          }
          currency = None
          if code is not None:
              currency = self.currency(code)
              request['ccy'] = currency['id']
          if since is not None:
              request['before'] = max(since - 1, 0)
          if limit is not None:
              request['limit'] = limit  # default 100, max 100
          response = self.privateGetAssetWithdrawalHistory(self.extend(request, params))
          #
          #     {
          #         "code": "0",
          #         "msg": "",
          #         "data": [
          #             {
          #                 "amt": "0.094",
          #                 "wdId": "4703879",
          #                 "fee": "0.01000000eth",
          #                 "txId": "0x62477bac6509a04512819bb1455e923a60dea5966c7caeaa0b24eb8fb0432b85",
          #                 "ccy": "ETH",
          #                 "from": "13426335357",
          #                 "to": "0xA41446125D0B5b6785f6898c9D67874D763A1519",
          #                 "ts": "1597026383085",
          #                 "state": "2"
          #             },
          #             {
          #                 "amt": "0.01",
          #                 "wdId": "4703879",
          #                 "fee": "0.00000000btc",
          #                 "txId": "",
          #                 "ccy": "BTC",
          #                 "from": "13426335357",
          #                 "to": "13426335357",
          #                 "ts": "1597026383085",
          #                 "state": "2"
          #             }
          #         ]
          #     }
          #
          data = self.safe_value(response, 'data', [])
          return self.parse_transactions(data, currency, since, limit, params)
  
      def parse_transaction_status(self, status):
          #
          # deposit statuses
          #
          #     {
          #         '0': 'waiting for confirmation',
          #         '1': 'deposit credited',
          #         '2': 'deposit successful'
          #     }
          #
          # withdrawal statuses
          #
          #     {
          #        '-3': 'pending cancel',
          #        '-2': 'canceled',
          #        '-1': 'failed',
          #         '0': 'pending',
          #         '1': 'sending',
          #         '2': 'sent',
          #         '3': 'awaiting email verification',
          #         '4': 'awaiting manual verification',
          #         '5': 'awaiting identity verification'
          #     }
          #
          statuses = {
              '-3': 'pending',
              '-2': 'canceled',
              '-1': 'failed',
              '0': 'pending',
              '1': 'pending',
              '2': 'ok',
              '3': 'pending',
              '4': 'pending',
              '5': 'pending',
          }
          return self.safe_string(statuses, status, status)
  
      def parse_transaction(self, transaction, currency=None):
          #
          # withdraw
          #
          #     {
          #         "amt": "0.1",
          #         "wdId": "67485",
          #         "ccy": "BTC"
          #     }
          #
          # fetchWithdrawals
          #
          #     {
          #         "amt": "0.094",
          #         "wdId": "4703879",
          #         "fee": "0.01000000eth",
          #         "txId": "0x62477bac6509a04512819bb1455e923a60dea5966c7caeaa0b24eb8fb0432b85",
          #         "ccy": "ETH",
          #         "from": "13426335357",
          #         "to": "0xA41446125D0B5b6785f6898c9D67874D763A1519",
          #         'tag': string,
          #         'pmtId': string,
          #         'memo': string,
          #         "ts": "1597026383085",
          #         "state": "2"
          #     }
          #
          # fetchDeposits
          #
          #     {
          #         "amt": "0.01044408",
          #         "txId": "1915737_3_0_0_asset",
          #         "ccy": "BTC",
          #         "from": "13801825426",
          #         "to": "",
          #         "ts": "1597026383085",
          #         "state": "2",
          #         "depId": "4703879"
          #     }
          #
          type = None
          id = None
          withdrawalId = self.safe_string(transaction, 'wdId')
          addressFrom = self.safe_string(transaction, 'from')
          addressTo = self.safe_string(transaction, 'to')
          address = addressTo
          tagTo = self.safe_string_2(transaction, 'tag', 'memo')
          tagTo = self.safe_string_2(transaction, 'pmtId', tagTo)
          if withdrawalId is not None:
              type = 'withdrawal'
              id = withdrawalId
          else:
              # the payment_id will appear on new deposits but appears to be removed from the response after 2 months
              id = self.safe_string(transaction, 'depId')
              type = 'deposit'
          currencyId = self.safe_string(transaction, 'ccy')
          code = self.safe_currency_code(currencyId)
          amount = self.safe_number(transaction, 'amt')
          status = self.parse_transaction_status(self.safe_string(transaction, 'state'))
          txid = self.safe_string(transaction, 'txId')
          timestamp = self.safe_integer(transaction, 'ts')
          feeCost = None
          if type == 'deposit':
              feeCost = 0
          else:
              feeCost = self.safe_number(transaction, 'fee')
          # todo parse tags
          return {
              'info': transaction,
              'id': id,
              'currency': code,
              'amount': amount,
              'network': None,
              'addressFrom': addressFrom,
              'addressTo': addressTo,
              'address': address,
              'tagFrom': None,
              'tagTo': tagTo,
              'tag': tagTo,
              'status': status,
              'type': type,
              'updated': None,
              'txid': txid,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'fee': {
                  'currency': code,
                  'cost': feeCost,
              },
          }
  
      def fetch_leverage(self, symbol, params={}):
          self.load_markets()
          marginMode = self.safe_string_lower(params, 'mgnMode')
          params = self.omit(params, ['mgnMode'])
          if (marginMode != 'cross') and (marginMode != 'isolated'):
              raise BadRequest(self.id + ' fetchLeverage params["mgnMode"] must be either cross or isolated')
          market = self.market(symbol)
          request = {
              'instId': market['id'],
              'mgnMode': marginMode,
          }
          response = self.privateGetAccountLeverageInfo(self.extend(request, params))
          #
          #     {
          #       "code": "0",
          #       "data": [
          #         {
          #           "instId": "BTC-USDT-SWAP",
          #           "lever": "5.00000000",
          #           "mgnMode": "isolated",
          #           "posSide": "net"
          #         }
          #       ],
          #       "msg": ""
          #     }
          #
          return response
  
      def fetch_position(self, symbol, params={}):
          self.load_markets()
          market = self.market(symbol)
          type = self.safe_string(params, 'type')
          params = self.omit(params, 'type')
          request = {
              # instType String No Instrument type, MARGIN, SWAP, FUTURES, OPTION
              'instId': market['id'],
              # posId String No Single position ID or multiple position IDs(no more than 20) separated with comma
          }
          if type is not None:
              request['instType'] = type.upper()
          params = self.omit(params, 'type')
          response = self.privateGetAccountPositions(params)
          #
          #     {
          #         "code": "0",
          #         "msg": "",
          #         "data": [
          #             {
          #                 "adl":"1",
          #                 "availPos":"1",
          #                 "avgPx":"2566.31",
          #                 "cTime":"1619507758793",
          #                 "ccy":"ETH",
          #                 "deltaBS":"",
          #                 "deltaPA":"",
          #                 "gammaBS":"",
          #                 "gammaPA":"",
          #                 "imr":"",
          #                 "instId":"ETH-USD-210430",
          #                 "instType":"FUTURES",
          #                 "interest":"0",
          #                 "last":"2566.22",
          #                 "lever":"10",
          #                 "liab":"",
          #                 "liabCcy":"",
          #                 "liqPx":"2352.8496681818233",
          #                 "margin":"0.0003896645377994",
          #                 "mgnMode":"isolated",
          #                 "mgnRatio":"11.731726509588816",
          #                 "mmr":"0.0000311811092368",
          #                 "optVal":"",
          #                 "pTime":"1619507761462",
          #                 "pos":"1",
          #                 "posCcy":"",
          #                 "posId":"307173036051017730",
          #                 "posSide":"long",
          #                 "thetaBS":"",
          #                 "thetaPA":"",
          #                 "tradeId":"109844",
          #                 "uTime":"1619507761462",
          #                 "upl":"-0.0000009932766034",
          #                 "uplRatio":"-0.0025490556801078",
          #                 "vegaBS":"",
          #                 "vegaPA":""
          #             }
          #         ]
          #     }
          #
          data = self.safe_value(response, 'data', [])
          position = self.safe_value(data, 0)
          if position is None:
              return position
          return self.parse_position(position)
  
      def fetch_positions(self, symbols=None, params={}):
          self.load_markets()
          # defaultType = self.safe_string_2(self.options, 'fetchPositions', 'defaultType')
          # type = self.safe_string(params, 'type', defaultType)
          type = self.safe_string(params, 'type')
          params = self.omit(params, 'type')
          request = {
              # instType String No Instrument type, MARGIN, SWAP, FUTURES, OPTION, instId will be checked against instType when both parameters are passed, and the position information of the instId will be returned.
              # instId String No Instrument ID, e.g. BTC-USD-190927-5000-C
              # posId String No Single position ID or multiple position IDs(no more than 20) separated with comma
          }
          if type is not None:
              request['instType'] = type.upper()
          params = self.omit(params, 'type')
          response = self.privateGetAccountPositions(self.extend(request, params))
          #
          #     {
          #         "code": "0",
          #         "msg": "",
          #         "data": [
          #             {
          #                 "adl":"1",
          #                 "availPos":"1",
          #                 "avgPx":"2566.31",
          #                 "cTime":"1619507758793",
          #                 "ccy":"ETH",
          #                 "deltaBS":"",
          #                 "deltaPA":"",
          #                 "gammaBS":"",
          #                 "gammaPA":"",
          #                 "imr":"",
          #                 "instId":"ETH-USD-210430",
          #                 "instType":"FUTURES",
          #                 "interest":"0",
          #                 "last":"2566.22",
          #                 "lever":"10",
          #                 "liab":"",
          #                 "liabCcy":"",
          #                 "liqPx":"2352.8496681818233",
          #                 "margin":"0.0003896645377994",
          #                 "mgnMode":"isolated",
          #                 "mgnRatio":"11.731726509588816",
          #                 "mmr":"0.0000311811092368",
          #                 "optVal":"",
          #                 "pTime":"1619507761462",
          #                 "pos":"1",
          #                 "posCcy":"",
          #                 "posId":"307173036051017730",
          #                 "posSide":"long",
          #                 "thetaBS":"",
          #                 "thetaPA":"",
          #                 "tradeId":"109844",
          #                 "uTime":"1619507761462",
          #                 "upl":"-0.0000009932766034",
          #                 "uplRatio":"-0.0025490556801078",
          #                 "vegaBS":"",
          #                 "vegaPA":""
          #             }
          #         ]
          #     }
          #
          positions = self.safe_value(response, 'data', [])
          result = []
          for i in range(0, len(positions)):
              entry = positions[i]
              instrument = self.safe_string(entry, 'instType')
              if (instrument == 'FUTURES') or instrument == ('SWAP'):
                  result.append(self.parse_position(positions[i]))
          return result
  
      def parse_position(self, position, market=None):
          #
          #     {
          #       "adl": "3",
          #       "availPos": "1",
          #       "avgPx": "34131.1",
          #       "cTime": "1627227626502",
          #       "ccy": "USDT",
          #       "deltaBS": "",
          #       "deltaPA": "",
          #       "gammaBS": "",
          #       "gammaPA": "",
          #       "imr": "170.66093041794787",
          #       "instId": "BTC-USDT-SWAP",
          #       "instType": "SWAP",
          #       "interest": "0",
          #       "last": "34134.4",
          #       "lever": "2",
          #       "liab": "",
          #       "liabCcy": "",
          #       "liqPx": "12608.959083877446",
          #       "markPx": "4786.459271773621",
          #       "margin": "",
          #       "mgnMode": "cross",
          #       "mgnRatio": "140.49930117599155",
          #       "mmr": "1.3652874433435829",
          #       "notionalUsd": "341.5130010779638",
          #       "optVal": "",
          #       "pos": "1",
          #       "posCcy": "",
          #       "posId": "339552508062380036",
          #       "posSide": "long",
          #       "thetaBS": "",
          #       "thetaPA": "",
          #       "tradeId": "98617799",
          #       "uTime": "1627227626502",
          #       "upl": "0.0108608358957281",
          #       "uplRatio": "0.0000636418743944",
          #       "vegaBS": "",
          #       "vegaPA": ""
          #     }
          #
          marketId = self.safe_string(position, 'instId')
          market = self.safe_market(marketId, market)
          symbol = market['symbol']
          contractsString = self.safe_string(position, 'pos')
          contractsAbs = Precise.string_abs(contractsString)
          contracts = None
          side = self.safe_string(position, 'posSide')
          hedged = side != 'net'
          if contractsString is not None:
              contracts = self.parse_number(contractsAbs)
              if side == 'net':
                  if Precise.string_gt(contractsString, '0'):
                      side = 'long'
                  else:
                      side = 'short'
          markPriceString = self.safe_string(position, 'markPx')
          notionalString = self.safe_string(position, 'notionalUsd')
          if market['inverse']:
              notionalString = Precise.string_div(notionalString, markPriceString)
          notional = self.parse_number(notionalString)
          marginType = self.safe_string(position, 'mgnMode')
          initialMarginString = None
          entryPriceString = self.safe_string(position, 'avgPx')
          unrealizedPnlString = self.safe_string(position, 'upl')
          leverageString = self.safe_string(position, 'lever')
          initialMarginPercentage = None
          collateralString = None
          if marginType == 'cross':
              initialMarginString = self.safe_string(position, 'imr')
              collateralString = Precise.string_add(initialMarginString, unrealizedPnlString)
          elif marginType == 'isolated':
              initialMarginPercentage = Precise.string_div('1', leverageString)
              collateralString = self.safe_string(position, 'margin')
          maintenanceMarginString = self.safe_string(position, 'mmr')
          maintenanceMargin = self.parse_number(maintenanceMarginString)
          maintenanceMarginPercentage = Precise.string_div(maintenanceMarginString, notionalString)
          if initialMarginPercentage is None:
              initialMarginPercentage = self.parse_number(Precise.string_div(initialMarginString, notionalString, 4))
          elif initialMarginString is None:
              initialMarginString = Precise.string_mul(initialMarginPercentage, notionalString)
          rounder = '0.00005'  # round to closest 0.01%
          maintenanceMarginPercentage = self.parse_number(Precise.string_div(Precise.string_add(maintenanceMarginPercentage, rounder), '1', 4))
          liquidationPrice = self.safe_number(position, 'liqPx')
          percentageString = self.safe_string(position, 'uplRatio')
          percentage = self.parse_number(Precise.string_mul(percentageString, '100'))
          timestamp = self.safe_integer(position, 'uTime')
          marginRatio = self.parse_number(Precise.string_div(maintenanceMarginString, collateralString, 4))
          return {
              'info': position,
              'symbol': symbol,
              'notional': notional,
              'marginType': marginType,
              'liquidationPrice': liquidationPrice,
              'entryPrice': self.parse_number(entryPriceString),
              'unrealizedPnl': self.parse_number(unrealizedPnlString),
              'percentage': percentage,
              'contracts': contracts,
              'contractSize': self.safe_value(market, 'contractSize'),
              'markPrice': self.parse_number(markPriceString),
              'side': side,
              'hedged': hedged,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'maintenanceMargin': maintenanceMargin,
              'maintenanceMarginPercentage': maintenanceMarginPercentage,
              'collateral': self.parse_number(collateralString),
              'initialMargin': self.parse_number(initialMarginString),
              'initialMarginPercentage': self.parse_number(initialMarginPercentage),
              'leverage': self.parse_number(leverageString),
              'marginRatio': marginRatio,
          }
  
      def transfer(self, code, amount, fromAccount, toAccount, params={}):
          self.load_markets()
          currency = self.currency(code)
          accountsByType = self.safe_value(self.options, 'accountsByType', {})
          fromId = self.safe_string(accountsByType, fromAccount)
          toId = self.safe_string(accountsByType, toAccount)
          if fromId is None:
              keys = list(accountsByType.keys())
              raise ExchangeError(self.id + ' fromAccount must be one of ' + ', '.join(keys))
          if toId is None:
              keys = list(accountsByType.keys())
              raise ExchangeError(self.id + ' toAccount must be one of ' + ', '.join(keys))
          request = {
              'ccy': currency['id'],
              'amt': self.currency_to_precision(code, amount),
              'type': '0',  # 0 = transfer within account by default, 1 = master account to sub-account, 2 = sub-account to master account
              'from': fromId,  # remitting account, 1 = SPOT, 3 = FUTURES, 5 = MARGIN, 6 = FUNDING, 9 = SWAP, 12 = OPTION, 18 = Unified account
              'to': toId,  # beneficiary account, 1 = SPOT, 3 = FUTURES, 5 = MARGIN, 6 = FUNDING, 9 = SWAP, 12 = OPTION, 18 = Unified account
              # 'subAcct': 'sub-account-name',  # optional, only required when type is 1 or 2
              # 'instId': market['id'],  # required when from is 3, 5 or 9, margin trading pair like BTC-USDT or contract underlying like BTC-USD to be transferred out
              # 'toInstId': market['id'],  # required when from is 3, 5 or 9, margin trading pair like BTC-USDT or contract underlying like BTC-USD to be transferred in
          }
          response = self.privatePostAssetTransfer(self.extend(request, params))
          #
          #     {
          #         "code": "0",
          #         "msg": "",
          #         "data": [
          #             {
          #                 "transId": "754147",
          #                 "ccy": "USDT",
          #                 "from": "6",
          #                 "amt": "0.1",
          #                 "to": "18"
          #             }
          #         ]
          #     }
          #
          data = self.safe_value(response, 'data', [])
          rawTransfer = self.safe_value(data, 0, {})
          return self.parse_transfer(rawTransfer, currency)
  
      def parse_transfer(self, transfer, currency=None):
          #
          # transfer
          #
          #     {
          #         "transId": "754147",
          #         "ccy": "USDT",
          #         "from": "6",
          #         "amt": "0.1",
          #         "to": "18"
          #     }
          #
          id = self.safe_string(transfer, 'transId')
          currencyId = self.safe_string(transfer, 'ccy')
          code = self.safe_currency_code(currencyId, currency)
          amount = self.safe_number(transfer, 'amt')
          fromAccountId = self.safe_string(transfer, 'from')
          toAccountId = self.safe_string(transfer, 'to')
          typesByAccount = self.safe_value(self.options, 'typesByAccount', {})
          fromAccount = self.safe_string(typesByAccount, fromAccountId)
          toAccount = self.safe_string(typesByAccount, toAccountId)
          timestamp = None
          status = None
          return {
              'info': transfer,
              'id': id,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'currency': code,
              'amount': amount,
              'fromAccount': fromAccount,
              'toAccount': toAccount,
              'status': status,
          }
  
      def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
          isArray = isinstance(params, list)
          request = '/api/' + self.version + '/' + self.implode_params(path, params)
          query = self.omit(params, self.extract_params(path))
          url = self.implode_hostname(self.urls['api']['rest']) + request
          # type = self.getPathAuthenticationType(path)
          if api == 'public':
              if query:
                  url += '?' + self.urlencode(query)
          elif api == 'private':
              self.check_required_credentials()
              timestamp = self.iso8601(self.milliseconds())
              headers = {
                  'OK-ACCESS-KEY': self.apiKey,
                  'OK-ACCESS-PASSPHRASE': self.password,
                  'OK-ACCESS-TIMESTAMP': timestamp,
                  # 'OK-FROM': '',
                  # 'OK-TO': '',
                  # 'OK-LIMIT': '',
              }
              auth = timestamp + method + request
              if method == 'GET':
                  if query:
                      urlencodedQuery = '?' + self.urlencode(query)
                      url += urlencodedQuery
                      auth += urlencodedQuery
              else:
                  if isArray or query:
                      body = self.json(query)
                      auth += body
                  headers['Content-Type'] = 'application/json'
              signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256, 'base64')
              headers['OK-ACCESS-SIGN'] = signature
          return {'url': url, 'method': method, 'body': body, 'headers': headers}
  
      def parse_funding_rate(self, fundingRate, market=None):
          #
          #     {
          #       "fundingRate": "0.00027815",
          #       "fundingTime": "1634256000000",
          #       "instId": "BTC-USD-SWAP",
          #       "instType": "SWAP",
          #       "nextFundingRate": "0.00017",
          #       "nextFundingTime": "1634284800000"
          #     }
          #
          # in the response above nextFundingRate is actually two funding rates from now
          #
          nextFundingRateTimestamp = self.safe_integer(fundingRate, 'nextFundingTime')
          marketId = self.safe_string(fundingRate, 'instId')
          symbol = self.safe_symbol(marketId, market)
          nextFundingRate = self.safe_number(fundingRate, 'nextFundingRate')
          fundingTime = self.safe_integer(fundingRate, 'fundingTime')
          # https://www.okex.com/support/hc/en-us/articles/360053909272-Ⅸ-Introduction-to-perpetual-swap-funding-fee
          # > The current interest is 0.
          return {
              'info': fundingRate,
              'symbol': symbol,
              'markPrice': None,
              'indexPrice': None,
              'interestRate': self.parse_number('0'),
              'estimatedSettlePrice': None,
              'timestamp': None,
              'datetime': None,
              'fundingRate': self.safe_number(fundingRate, 'fundingRate'),
              'fundingTimestamp': fundingTime,
              'fundingDatetime': self.iso8601(fundingTime),
              'nextFundingRate': nextFundingRate,
              'nextFundingTimestamp': nextFundingRateTimestamp,
              'nextFundingDatetime': self.iso8601(nextFundingRateTimestamp),
              'previousFundingRate': None,
              'previousFundingTimestamp': None,
              'previousFundingDatetime': None,
          }
  
      def fetch_funding_rate(self, symbol, params={}):
          self.load_markets()
          market = self.market(symbol)
          if not market['swap']:
              raise ExchangeError(self.id + ' fetchFundingRate is only valid for swap markets')
          request = {
              'instId': market['id'],
          }
          response = self.publicGetPublicFundingRate(self.extend(request, params))
          #
          #     {
          #       "code": "0",
          #       "data": [
          #         {
          #           "fundingRate": "0.00027815",
          #           "fundingTime": "1634256000000",
          #           "instId": "BTC-USD-SWAP",
          #           "instType": "SWAP",
          #           "nextFundingRate": "0.00017",
          #           "nextFundingTime": "1634284800000"
          #         }
          #       ],
          #       "msg": ""
          #     }
          #
          data = self.safe_value(response, 'data', [])
          entry = self.safe_value(data, 0, {})
          return self.parse_funding_rate(entry, market)
  
      def fetch_funding_history(self, symbol=None, since=None, limit=None, params={}):
          self.load_markets()
          request = {
              # 'instType': 'SPOT',  # SPOT, MARGIN, SWAP, FUTURES, OPTION
              # 'ccy': currency['id'],
              # 'mgnMode': 'isolated',  # isolated, cross
              # 'ctType': 'linear',  # linear, inverse, only applicable to FUTURES/SWAP
              'type': '8',
              #
              # supported values for type
              #
              #     1 Transfer
              #     2 Trade
              #     3 Delivery
              #     4 Auto token conversion
              #     5 Liquidation
              #     6 Margin transfer
              #     7 Interest deduction
              #     8 Funding fee
              #     9 ADL
              #     10 Clawback
              #     11 System token conversion
              #     12 Strategy transfer
              #     13 ddh
              #
              # 'subType': '',
              #
              # supported values for subType
              #
              #     1 Buy
              #     2 Sell
              #     3 Open long
              #     4 Open short
              #     5 Close long
              #     6 Close short
              #     9 Interest deduction
              #     11 Transfer in
              #     12 Transfer out
              #     160 Manual margin increase
              #     161 Manual margin decrease
              #     162 Auto margin increase
              #     110 Auto buy
              #     111 Auto sell
              #     118 System token conversion transfer in
              #     119 System token conversion transfer out
              #     100 Partial liquidation close long
              #     101 Partial liquidation close short
              #     102 Partial liquidation buy
              #     103 Partial liquidation sell
              #     104 Liquidation long
              #     105 Liquidation short
              #     106 Liquidation buy
              #     107 Liquidation sell
              #     110 Liquidation transfer in
              #     111 Liquidation transfer out
              #     125 ADL close long
              #     126 ADL close short
              #     127 ADL buy
              #     128 ADL sell
              #     131 ddh buy
              #     132 ddh sell
              #     170 Exercised
              #     171 Counterparty exercised
              #     172 Expired OTM
              #     112 Delivery long
              #     113 Delivery short
              #     117 Delivery/Exercise clawback
              #     173 Funding fee expense
              #     174 Funding fee income
              #     200 System transfer in
              #     201 Manually transfer in
              #     202 System transfer out
              #     203 Manually transfer out
              #
              # 'after': 'id',  # earlier than the requested bill ID
              # 'before': 'id',  # newer than the requested bill ID
              # 'limit': '100',  # default 100, max 100
          }
          if limit is not None:
              request['limit'] = str(limit)  # default 100, max 100
          response = self.privateGetAccountBills(self.extend(request, params))
          #
          #     {
          #       "bal": "0.0242946200998573",
          #       "balChg": "0.0000148752712240",
          #       "billId": "377970609204146187",
          #       "ccy": "ETH",
          #       "execType": "",
          #       "fee": "0",
          #       "from": "",
          #       "instId": "ETH-USD-SWAP",
          #       "instType": "SWAP",
          #       "mgnMode": "isolated",
          #       "notes": "",
          #       "ordId": "",
          #       "pnl": "0.000014875271224",
          #       "posBal": "0",
          #       "posBalChg": "0",
          #       "subType": "174",
          #       "sz": "9",
          #       "to": "",
          #       "ts": "1636387215588",
          #       "type": "8"
          #     }
          #
          data = self.safe_value(response, 'data')
          result = []
          for i in range(0, len(data)):
              entry = data[i]
              timestamp = self.safe_integer(entry, 'ts')
              instId = self.safe_string(entry, 'instId')
              market = self.safe_market(instId)
              result.append({
                  'info': entry,
                  'symbol': market['symbol'],
                  'code': market['base'] if market['inverse'] else market['quote'],
                  'timestamp': timestamp,
                  'datetime': self.iso8601(timestamp),
                  'id': self.safe_string(entry, 'billId'),
                  'amount': self.safe_number(entry, 'balChg'),
              })
          sorted = self.sort_by(result, 'timestamp')
          return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)
  
      def set_leverage(self, leverage, symbol=None, params={}):
          if symbol is None:
              raise ArgumentsRequired(self.id + ' setLeverage() requires a symbol argument')
          # WARNING: THIS WILL INCREASE LIQUIDATION PRICE FOR OPEN ISOLATED LONG POSITIONS
          # AND DECREASE LIQUIDATION PRICE FOR OPEN ISOLATED SHORT POSITIONS
          if (leverage < 1) or (leverage > 125):
              raise BadRequest(self.id + ' setLeverage leverage should be between 1 and 125')
          self.load_markets()
          market = self.market(symbol)
          marginMode = self.safe_string_lower(params, 'mgnMode')
          params = self.omit(params, ['mgnMode'])
          if (marginMode != 'cross') and (marginMode != 'isolated'):
              raise BadRequest(self.id + ' setLeverage params["mgnMode"] must be either cross or isolated')
          request = {
              'lever': leverage,
              'mgnMode': marginMode,
              'instId': market['id'],
          }
          response = self.privatePostAccountSetLeverage(self.extend(request, params))
          #
          #     {
          #       "code": "0",
          #       "data": [
          #         {
          #           "instId": "BTC-USDT-SWAP",
          #           "lever": "5",
          #           "mgnMode": "isolated",
          #           "posSide": "long"
          #         }
          #       ],
          #       "msg": ""
          #     }
          #
          return response
  
      def set_position_mode(self, hedged, symbol=None, params={}):
          hedgeMode = None
          if hedged:
              hedgeMode = 'long_short_mode'
          else:
              hedgeMode = 'net_mode'
          request = {
              'posMode': hedgeMode,
          }
          response = self.privatePostAccountSetPositionMode(self.extend(request, params))
          #
          #     {
          #       "code": "0",
          #       "data": [
          #         {
          #           "posMode": "net_mode"
          #         }
          #       ],
          #       "msg": ""
          #     }
          #
          return response
  
      def set_margin_mode(self, marginType, symbol=None, params={}):
          if symbol is None:
              raise ArgumentsRequired(self.id + ' setLeverage() requires a symbol argument')
          # WARNING: THIS WILL INCREASE LIQUIDATION PRICE FOR OPEN ISOLATED LONG POSITIONS
          # AND DECREASE LIQUIDATION PRICE FOR OPEN ISOLATED SHORT POSITIONS
          marginType = marginType.lower()
          if (marginType != 'cross') and (marginType != 'isolated'):
              raise BadRequest(self.id + ' setMarginMode marginType must be either cross or isolated')
          self.load_markets()
          market = self.market(symbol)
          lever = self.safe_integer(params, 'lever')
          if (lever is None) or (lever < 1) or (lever > 125):
              raise BadRequest(self.id + ' setMarginMode params["lever"] should be between 1 and 125')
          params = self.omit(params, ['lever'])
          request = {
              'lever': lever,
              'mgnMode': marginType,
              'instId': market['id'],
          }
          response = self.privatePostAccountSetLeverage(self.extend(request, params))
          #
          #     {
          #       "code": "0",
          #       "data": [
          #         {
          #           "instId": "BTC-USDT-SWAP",
          #           "lever": "5",
          #           "mgnMode": "isolated",
          #           "posSide": "long"
          #         }
          #       ],
          #       "msg": ""
          #     }
          #
          return response
  
      def modify_margin_helper(self, symbol, amount, type, params={}):
          self.load_markets()
          market = self.market(symbol)
          posSide = self.safe_string(params, 'posSide', 'net')
          params = self.omit(params, ['posSide'])
          request = {
              'instId': market['id'],
              'amt': amount,
              'type': type,
              'posSide': posSide,
          }
          response = self.privatePostAccountPositionMarginBalance(self.extend(request, params))
          #
          #     {
          #       "code": "0",
          #       "data": [
          #         {
          #           "amt": "0.01",
          #           "instId": "ETH-USD-SWAP",
          #           "posSide": "net",
          #           "type": "reduce"
          #         }
          #       ],
          #       "msg": ""
          #     }
          #
          data = self.safe_value(response, 'data', [])
          entry = self.safe_value(data, 0, {})
          errorCode = self.safe_string(response, 'code')
          status = 'ok' if (errorCode == '0') else 'failed'
          responseAmount = self.safe_number(entry, 'amt')
          responseType = self.safe_string(entry, 'type')
          marketId = self.safe_string(entry, 'instId')
          responseMarket = self.safe_market(marketId, market)
          code = responseMarket['base'] if responseMarket['inverse'] else responseMarket['quote']
          symbol = responseMarket['symbol']
          return {
              'info': response,
              'type': responseType,
              'amount': responseAmount,
              'code': code,
              'symbol': symbol,
              'status': status,
          }
  
      def fetch_borrow_rates(self, params={}):
          self.load_markets()
          response = self.privateGetAccountInterestRate(params)
          # {
          #     "code": "0",
          #     "data": [
          #         {
          #             "ccy":"BTC",
          #             "interestRate":"0.00000833"
          #         }
          #         ...
          #     ],
          # }
          timestamp = self.milliseconds()
          data = self.safe_value(response, 'data')
          rates = {}
          for i in range(0, len(data)):
              rate = data[i]
              code = self.safe_currency_code(self.safe_string(rate, 'ccy'))
              rates[code] = {
                  'currency': code,
                  'rate': self.safe_number(rate, 'interestRate'),
                  'period': 86400000,
                  'timestamp': timestamp,
                  'datetime': self.iso8601(timestamp),
                  'info': rate,
              }
          return rates
  
      def fetch_borrow_rate(self, code, params={}):
          self.load_markets()
          currency = self.currency(code)
          request = {
              'ccy': currency['id'],
          }
          response = self.privateGetAccountInterestRate(self.extend(request, params))
          # {
          #     "code": "0",
          #     "data":[
          #          {
          #             "ccy":"USDT",
          #             "interestRate":"0.00002065"
          #          }
          #          ...
          #     ],
          #     "msg":""
          # }
          timestamp = self.milliseconds()
          data = self.safe_value(response, 'data')
          rate = self.safe_value(data, 0)
          return {
              'currency': code,
              'rate': self.safe_number(rate, 'interestRate'),
              'period': 86400000,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'info': rate,
          }
  
      def reduce_margin(self, symbol, amount, params={}):
          return self.modify_margin_helper(symbol, amount, 'reduce', params)
  
      def add_margin(self, symbol, amount, params={}):
          return self.modify_margin_helper(symbol, amount, 'add', params)
  
      def set_sandbox_mode(self, enable):
          if enable:
              self.headers['x-simulated-trading'] = '1'
          elif 'x-simulated-trading' in self.headers:
              self.headers = self.omit(self.headers, 'x-simulated-trading')
  
      def handle_errors(self, httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody):
          if not response:
              return  # fallback to default error handler
          #
          #     {"code":"1","data":[{"clOrdId":"","ordId":"","sCode":"51119","sMsg":"Order placement failed due to insufficient balance. ","tag":""}],"msg":""}
          #     {"code":"58001","data":[],"msg":"Incorrect trade password"}
          #
          code = self.safe_string(response, 'code')
          if code != '0':
              feedback = self.id + ' ' + body
              data = self.safe_value(response, 'data', [])
              for i in range(0, len(data)):
                  error = data[i]
                  errorCode = self.safe_string(error, 'sCode')
                  message = self.safe_string(error, 'sMsg')
                  self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
                  self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
              self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
              raise ExchangeError(feedback)  # unknown message
  