<?php

namespace ccxt\async;

// 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

use Exception; // a common import
use ccxt\async\abstract\okx as Exchange;
use ccxt\ExchangeError;
use ccxt\ArgumentsRequired;
use ccxt\BadRequest;
use ccxt\InvalidAddress;
use ccxt\InvalidOrder;
use ccxt\NotSupported;
use ccxt\Precise;
use \React\Async;
use \React\Promise;
use \React\Promise\PromiseInterface;

class okx extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'okx',
            'name' => 'OKX',
            'countries' => array( 'CN', 'US' ),
            'version' => 'v5',
            'rateLimit' => 100 * 1.10, // 10% tolerance because of #26973
            'pro' => true,
            'certified' => true,
            'has' => array(
                'CORS' => null,
                'spot' => true,
                'margin' => true,
                'swap' => true,
                'future' => true,
                'option' => true,
                'addMargin' => true,
                'cancelAllOrders' => false,
                'cancelAllOrdersAfter' => true,
                'cancelOrder' => true,
                'cancelOrders' => true,
                'cancelOrdersForSymbols' => true,
                'closeAllPositions' => false,
                'closePosition' => true,
                'createConvertTrade' => true,
                'createDepositAddress' => false,
                'createMarketBuyOrderWithCost' => true,
                'createMarketSellOrderWithCost' => true,
                'createOrder' => true,
                'createOrders' => true,
                'createOrderWithTakeProfitAndStopLoss' => true,
                'createPostOnlyOrder' => true,
                'createReduceOnlyOrder' => true,
                'createStopLimitOrder' => true,
                'createStopLossOrder' => true,
                'createStopMarketOrder' => true,
                'createStopOrder' => true,
                'createTakeProfitOrder' => true,
                'createTrailingPercentOrder' => true,
                'createTriggerOrder' => true,
                'editOrder' => true,
                'fetchAccounts' => true,
                'fetchAllGreeks' => true,
                'fetchBalance' => true,
                'fetchBidsAsks' => null,
                'fetchBorrowInterest' => true,
                'fetchBorrowRateHistories' => true,
                'fetchBorrowRateHistory' => true,
                'fetchCanceledOrders' => true,
                'fetchClosedOrder' => null,
                'fetchClosedOrders' => true,
                'fetchConvertCurrencies' => true,
                'fetchConvertQuote' => true,
                'fetchConvertTrade' => true,
                'fetchConvertTradeHistory' => true,
                'fetchCrossBorrowRate' => true,
                'fetchCrossBorrowRates' => true,
                'fetchCurrencies' => true,
                'fetchDeposit' => true,
                'fetchDepositAddress' => true,
                'fetchDepositAddresses' => false,
                'fetchDepositAddressesByNetwork' => true,
                'fetchDeposits' => true,
                'fetchDepositsWithdrawals' => false,
                'fetchDepositWithdrawFee' => 'emulated',
                'fetchDepositWithdrawFees' => true,
                'fetchFundingHistory' => true,
                'fetchFundingInterval' => true,
                'fetchFundingIntervals' => false,
                'fetchFundingRate' => true,
                'fetchFundingRateHistory' => true,
                'fetchFundingRates' => true,
                'fetchGreeks' => true,
                'fetchIndexOHLCV' => true,
                'fetchIsolatedBorrowRate' => false,
                'fetchIsolatedBorrowRates' => false,
                'fetchL3OrderBook' => false,
                'fetchLedger' => true,
                'fetchLedgerEntry' => null,
                'fetchLeverage' => true,
                'fetchLeverageTiers' => false,
                'fetchLongShortRatio' => false,
                'fetchLongShortRatioHistory' => true,
                'fetchMarginAdjustmentHistory' => true,
                'fetchMarketLeverageTiers' => true,
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => true,
                'fetchMarkPrice' => true,
                'fetchMarkPrices' => true,
                'fetchMySettlementHistory' => false,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenInterest' => true,
                'fetchOpenInterestHistory' => true,
                'fetchOpenInterests' => true,
                'fetchOpenOrder' => null,
                'fetchOpenOrders' => true,
                'fetchOption' => true,
                'fetchOptionChain' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrderBooks' => false,
                'fetchOrders' => false,
                'fetchOrderTrades' => true,
                'fetchPosition' => true,
                'fetchPositionHistory' => 'emulated',
                'fetchPositions' => true,
                'fetchPositionsForSymbol' => true,
                'fetchPositionsHistory' => true,
                'fetchPositionsRisk' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchSettlementHistory' => true,
                'fetchStatus' => true,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => true,
                'fetchTradingFees' => false,
                'fetchTradingLimits' => false,
                'fetchTransactionFee' => false,
                'fetchTransactionFees' => false,
                'fetchTransactions' => false,
                'fetchTransfer' => true,
                'fetchTransfers' => true,
                'fetchUnderlyingAssets' => true,
                'fetchVolatilityHistory' => false,
                'fetchWithdrawal' => true,
                'fetchWithdrawals' => true,
                'fetchWithdrawalWhitelist' => false,
                'reduceMargin' => true,
                'repayCrossMargin' => true,
                'sandbox' => true,
                'setLeverage' => true,
                'setMargin' => false,
                'setMarginMode' => true,
                'setPositionMode' => true,
                'signIn' => false,
                'transfer' => true,
                'withdraw' => true,
            ),
            'timeframes' => array(
                '1m' => '1m',
                '3m' => '3m',
                '5m' => '5m',
                '15m' => '15m',
                '30m' => '30m',
                '1h' => '1H',
                '2h' => '2H',
                '4h' => '4H',
                '6h' => '6H',
                '12h' => '12H',
                '1d' => '1D',
                '1w' => '1W',
                '1M' => '1M',
                '3M' => '3M',
            ),
            'hostname' => 'www.okx.com', // or aws.okx.com
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/1294454/152485636-38b19e4a-bece-4dec-979a-5982859ffc04.jpg',
                'api' => array(
                    'rest' => 'https://{hostname}',
                ),
                'www' => 'https://www.okx.com',
                'doc' => 'https://www.okx.com/docs-v5/en/',
                'fees' => 'https://www.okx.com/pages/products/fees.html',
                'referral' => array(
                    // old reflink 0% discount https://www.okx.com/join/1888677
                    // new reflink 20% discount https://www.okx.com/join/CCXT2023
                    'url' => 'https://www.okx.com/join/CCXT2023',
                    'discount' => 0.2,
                ),
                'test' => array(
                    'rest' => 'https://{hostname}',
                ),
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'market/books-full' => 2,
                        'market/tickers' => 1,
                        'market/ticker' => 1,
                        'market/index-tickers' => 1,
                        'market/books' => 1 / 2,
                        'market/books-lite' => 5 / 3,
                        'market/candles' => 1 / 2,
                        'market/history-candles' => 1,
                        'market/index-candles' => 1,
                        'market/history-index-candles' => 2,
                        'market/mark-price-candles' => 1,
                        'market/history-mark-price-candles' => 2,
                        'market/trades' => 1 / 5,
                        'market/history-trades' => 2,
                        'market/option/instrument-family-trades' => 1,
                        'market/platform-24-volume' => 10,
                        'market/open-oracle' => 50,
                        'market/exchange-rate' => 20,
                        'market/index-components' => 1,
                        'public/market-data-history' => 4,
                        'public/economic-calendar' => 50,
                        'market/block-tickers' => 1,
                        'market/block-ticker' => 1,
                        'public/block-trades' => 1,
                        'public/instruments' => 1,
                        'public/delivery-exercise-history' => 1 / 2,
                        '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/mark-price' => 2,
                        'public/position-tiers' => 2,
                        'public/interest-rate-loan-quota' => 10,
                        'public/vip-interest-rate-loan-quota' => 10,
                        'public/underlying' => 1,
                        'public/insurance-fund' => 2,
                        'public/convert-contract-coin' => 2,
                        'public/option-trades' => 1,
                        'public/instrument-tick-bands' => 4,
                        '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/long-short-account-ratio-contract' => 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' => 50,
                        // public api
                        'sprd/spreads' => 1,
                        'sprd/books' => 1 / 2,
                        'sprd/ticker' => 1,
                        'sprd/public-trades' => 1 / 5,
                        'market/sprd-ticker' => 2,
                        'market/sprd-candles' => 2,
                        'market/sprd-history-candles' => 2,
                        'tradingBot/grid/ai-param' => 1,
                        'tradingBot/grid/min-investment' => 1,
                        'tradingBot/public/rsi-back-testing' => 1,
                        'asset/exchange-list' => 5 / 3,
                        'finance/staking-defi/eth/apy-history' => 5 / 3,
                        'finance/staking-defi/sol/apy-history' => 5 / 3,
                        'finance/savings/lending-rate-summary' => 5 / 3,
                        'finance/savings/lending-rate-history' => 5 / 3,
                        'finance/fixed-loan/lending-offers' => 10 / 3,
                        'finance/fixed-loan/lending-apy-history' => 10 / 3,
                        'finance/fixed-loan/pending-lending-volume' => 10 / 3,
                        // public broker
                        'finance/sfp/dcd/products' => 2 / 3,
                        // copytrading
                        'copytrading/public-lead-traders' => 4,
                        'copytrading/public-weekly-pnl' => 4,
                        'copytrading/public-stats' => 4,
                        'copytrading/public-preference-currency' => 4,
                        'copytrading/public-current-subpositions' => 4,
                        'copytrading/public-subpositions-history' => 4,
                        'support/announcements-types' => 20,
                    ),
                ),
                'private' => array(
                    'get' => array(
                        // rfq
                        'rfq/counterparties' => 4,
                        'rfq/maker-instrument-settings' => 4,
                        'rfq/mmp-config' => 4,
                        'rfq/rfqs' => 10,
                        'rfq/quotes' => 10,
                        'rfq/trades' => 4,
                        'rfq/public-trades' => 4,
                        // sprd
                        'sprd/order' => 1 / 3,
                        'sprd/orders-pending' => 1 / 3,
                        'sprd/orders-history' => 1 / 2,
                        'sprd/orders-history-archive' => 1 / 2,
                        'sprd/trades' => 1 / 3,
                        // trade
                        'trade/order' => 1 / 3,
                        'trade/orders-pending' => 1 / 3,
                        'trade/orders-history' => 1 / 2,
                        'trade/orders-history-archive' => 1,
                        'trade/fills' => 1 / 3,
                        'trade/fills-history' => 2.2,
                        'trade/fills-archive' => 2,
                        'trade/order-algo' => 1,
                        'trade/orders-algo-pending' => 1,
                        'trade/orders-algo-history' => 1,
                        'trade/easy-convert-currency-list' => 20,
                        'trade/easy-convert-history' => 20,
                        'trade/one-click-repay-currency-list' => 20,
                        'trade/one-click-repay-currency-list-v2' => 20,
                        'trade/one-click-repay-history' => 20,
                        'trade/one-click-repay-history-v2' => 20,
                        'trade/account-rate-limit' => 1,
                        // asset
                        'asset/currencies' => 5 / 3,
                        'asset/balances' => 5 / 3,
                        'asset/non-tradable-assets' => 5 / 3,
                        'asset/asset-valuation' => 10,
                        'asset/transfer-state' => 10,
                        'asset/bills' => 5 / 3,
                        'asset/deposit-lightning' => 5,
                        'asset/deposit-address' => 5 / 3,
                        'asset/deposit-history' => 5 / 3,
                        'asset/withdrawal-history' => 5 / 3,
                        'asset/deposit-withdraw-status' => 20,
                        'asset/convert/currencies' => 5 / 3,
                        'asset/convert/currency-pair' => 5 / 3,
                        'asset/convert/history' => 5 / 3,
                        'asset/monthly-statement' => 2,
                        // account
                        'account/instruments' => 1,
                        'account/balance' => 2,
                        'account/positions' => 2,
                        'account/positions-history' => 100,
                        'account/account-position-risk' => 2,
                        'account/bills' => 5 / 3,
                        'account/bills-archive' => 5 / 3,
                        'account/bills-history-archive' => 2,
                        'account/config' => 4,
                        'account/max-size' => 1,
                        'account/max-avail-size' => 1,
                        'account/leverage-info' => 1,
                        'account/adjust-leverage-info' => 4,
                        '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/quick-margin-borrow-repay-history' => 4,
                        'account/borrow-repay-history' => 4,
                        'account/vip-interest-accrued' => 4,
                        'account/vip-interest-deducted' => 4,
                        'account/vip-loan-order-list' => 4,
                        'account/vip-loan-order-detail' => 4,
                        'account/interest-limits' => 4,
                        'account/greeks' => 2,
                        'account/position-tiers' => 2,
                        'account/mmp-config' => 4,
                        'account/fixed-loan/borrowing-limit' => 4,
                        'account/fixed-loan/borrowing-quote' => 5,
                        'account/fixed-loan/borrowing-orders-list' => 5,
                        'account/spot-manual-borrow-repay' => 30,
                        'account/set-auto-repay' => 4,
                        'account/spot-borrow-repay-history' => 4,
                        'account/move-positions-history' => 10,
                        // subaccount
                        'users/subaccount/list' => 10,
                        'account/subaccount/balances' => 10 / 3,
                        'asset/subaccount/balances' => 10 / 3,
                        'account/subaccount/max-withdrawal' => 1,
                        'asset/subaccount/bills' => 5 / 3,
                        'asset/subaccount/managed-subaccount-bills' => 5 / 3,
                        'users/entrust-subaccount-list' => 10,
                        'account/subaccount/interest-limits' => 4,
                        'users/subaccount/apikey' => 10,
                        // grid trading
                        'tradingBot/grid/orders-algo-pending' => 1,
                        'tradingBot/grid/orders-algo-history' => 1,
                        'tradingBot/grid/orders-algo-details' => 1,
                        'tradingBot/grid/sub-orders' => 1,
                        'tradingBot/grid/positions' => 1,
                        'tradingBot/grid/ai-param' => 1,
                        'tradingBot/signal/signals' => 1,
                        'tradingBot/signal/orders-algo-details' => 1,
                        'tradingBot/signal/orders-algo-history' => 1,
                        'tradingBot/signal/positions' => 1,
                        'tradingBot/signal/positions-history' => 1,
                        'tradingBot/signal/sub-orders' => 1,
                        'tradingBot/signal/event-history' => 1,
                        'tradingBot/recurring/orders-algo-pending' => 1,
                        'tradingBot/recurring/orders-algo-history' => 1,
                        'tradingBot/recurring/orders-algo-details' => 1,
                        'tradingBot/recurring/sub-orders' => 1,
                        // earn
                        'finance/savings/balance' => 5 / 3,
                        'finance/savings/lending-history' => 5 / 3,
                        'finance/staking-defi/offers' => 10 / 3,
                        'finance/staking-defi/orders-active' => 10 / 3,
                        'finance/staking-defi/orders-history' => 10 / 3,
                        // eth staking
                        'finance/staking-defi/eth/balance' => 5 / 3,
                        'finance/staking-defi/eth/purchase-redeem-history' => 5 / 3,
                        'finance/staking-defi/eth/product-info' => 3,
                        'finance/staking-defi/sol/balance' => 5 / 3,
                        'finance/staking-defi/sol/purchase-redeem-history' => 5 / 3,
                        // copytrading
                        'copytrading/current-subpositions' => 1,
                        'copytrading/subpositions-history' => 1,
                        'copytrading/instruments' => 4,
                        'copytrading/profit-sharing-details' => 4,
                        'copytrading/total-profit-sharing' => 4,
                        'copytrading/unrealized-profit-sharing-details' => 4,
                        'copytrading/copy-settings' => 4,
                        'copytrading/batch-leverage-info' => 4,
                        'copytrading/current-lead-traders' => 4,
                        'copytrading/lead-traders-history' => 4,
                        // broker
                        'broker/nd/info' => 10,
                        'broker/nd/subaccount-info' => 10,
                        'broker/nd/subaccount/apikey' => 10,
                        'asset/broker/nd/subaccount-deposit-address' => 5 / 3,
                        'asset/broker/nd/subaccount-deposit-history' => 4,
                        'asset/broker/nd/subaccount-withdrawal-history' => 4,
                        'broker/nd/rebate-daily' => 100,
                        'broker/nd/rebate-per-orders' => 300,
                        'finance/sfp/dcd/order' => 2,
                        'finance/sfp/dcd/orders' => 2,
                        'broker/fd/rebate-per-orders' => 300,
                        'broker/fd/if-rebate' => 5,
                        // affiliate
                        'affiliate/invitee/detail' => 1,
                        'users/partner/if-rebate' => 1,
                        'support/announcements' => 4,
                    ),
                    'post' => array(
                        // rfq
                        'rfq/create-rfq' => 4,
                        'rfq/cancel-rfq' => 4,
                        'rfq/cancel-batch-rfqs' => 10,
                        'rfq/cancel-all-rfqs' => 10,
                        'rfq/execute-quote' => 15,
                        'rfq/maker-instrument-settings' => 4,
                        'rfq/mmp-reset' => 4,
                        'rfq/mmp-config' => 100,
                        'rfq/create-quote' => 0.4,
                        'rfq/cancel-quote' => 0.4,
                        'rfq/cancel-batch-quotes' => 10,
                        'rfq/cancel-all-quotes' => 10,
                        // sprd
                        'sprd/order' => 1,
                        'sprd/cancel-order' => 1,
                        'sprd/mass-cancel' => 1,
                        'sprd/amend-order' => 1,
                        'sprd/cancel-all-after' => 10,
                        // trade
                        'trade/order' => 1 / 3,
                        'trade/batch-orders' => 1 / 15,
                        'trade/cancel-order' => 1 / 3,
                        'trade/cancel-batch-orders' => 1 / 15,
                        'trade/amend-order' => 1 / 3,
                        'trade/amend-batch-orders' => 1 / 150,
                        'trade/close-position' => 1,
                        'trade/fills-archive' => 172800, // 5 req per day = 5/24/60/60 => 10/5*24*60*60=172800
                        'trade/order-algo' => 1,
                        'trade/cancel-algos' => 1,
                        'trade/amend-algos' => 1,
                        'trade/cancel-advance-algos' => 1,
                        'trade/easy-convert' => 20,
                        'trade/one-click-repay' => 20,
                        'trade/one-click-repay-v2' => 20,
                        'trade/mass-cancel' => 4,
                        'trade/cancel-all-after' => 10,
                        // asset
                        'asset/transfer' => 10,
                        'asset/withdrawal' => 5 / 3,
                        'asset/withdrawal-lightning' => 5,
                        'asset/cancel-withdrawal' => 5 / 3,
                        'asset/convert-dust-assets' => 10,
                        'asset/convert/estimate-quote' => 1,
                        'asset/convert/trade' => 1,
                        'asset/monthly-statement' => 1,
                        // account
                        'account/set-position-mode' => 4,
                        'account/set-leverage' => 1,
                        'account/position/margin-balance' => 1,
                        'account/set-greeks' => 4,
                        'account/set-isolated-mode' => 4,
                        'account/quick-margin-borrow-repay' => 4,
                        'account/borrow-repay' => 5 / 3,
                        'account/simulated_margin' => 10,
                        'account/position-builder' => 10,
                        'account/set-riskOffset-type' => 2,
                        'account/activate-option' => 4,
                        'account/set-auto-loan' => 4,
                        'account/set-account-level' => 4,
                        'account/mmp-reset' => 4,
                        'account/mmp-config' => 100,
                        'account/fixed-loan/borrowing-order' => 5,
                        'account/fixed-loan/amend-borrowing-order' => 5,
                        'account/fixed-loan/manual-reborrow' => 5,
                        'account/fixed-loan/repay-borrowing-order' => 5,
                        'account/bills-history-archive' => 72000, // 12 req/day
                        'account/move-positions' => 10,
                        'account/set-settle-currency' => 1,
                        // subaccount
                        'users/subaccount/modify-apikey' => 10,
                        'asset/subaccount/transfer' => 10,
                        'users/subaccount/set-transfer-out' => 10,
                        'account/subaccount/set-loan-allocation' => 4,
                        'users/subaccount/create-subaccount' => 10,
                        'users/subaccount/subaccount-apikey' => 10,
                        'users/subaccount/delete-apikey' => 10,
                        // grid trading
                        'tradingBot/grid/order-algo' => 1,
                        'tradingBot/grid/amend-order-algo' => 1,
                        'tradingBot/grid/stop-order-algo' => 1,
                        'tradingBot/grid/close-position' => 1,
                        'tradingBot/grid/cancel-close-order' => 1,
                        'tradingBot/grid/order-instant-trigger' => 1,
                        'tradingBot/grid/withdraw-income' => 1,
                        'tradingBot/grid/compute-margin-balance' => 1,
                        'tradingBot/grid/margin-balance' => 1,
                        'tradingBot/grid/min-investment' => 1,
                        'tradingBot/grid/adjust-investment' => 1,
                        'tradingBot/signal/create-signal' => 1,
                        'tradingBot/signal/order-algo' => 1,
                        'tradingBot/signal/stop-order-algo' => 1,
                        'tradingBot/signal/margin-balance' => 1,
                        'tradingBot/signal/amendTPSL' => 1,
                        'tradingBot/signal/set-instruments' => 1,
                        'tradingBot/signal/close-position' => 1,
                        'tradingBot/signal/sub-order' => 1,
                        'tradingBot/signal/cancel-sub-order' => 1,
                        'tradingBot/recurring/order-algo' => 1,
                        'tradingBot/recurring/amend-order-algo' => 1,
                        'tradingBot/recurring/stop-order-algo' => 1,
                        // earn
                        'finance/savings/purchase-redempt' => 5 / 3,
                        'finance/savings/set-lending-rate' => 5 / 3,
                        'finance/staking-defi/purchase' => 3,
                        'finance/staking-defi/redeem' => 3,
                        'finance/staking-defi/cancel' => 3,
                        // eth staking
                        'finance/staking-defi/eth/purchase' => 5,
                        'finance/staking-defi/eth/redeem' => 5,
                        'finance/staking-defi/sol/purchase' => 5,
                        'finance/staking-defi/sol/redeem' => 5,
                        // copytrading
                        'copytrading/algo-order' => 1,
                        'copytrading/close-subposition' => 1,
                        'copytrading/set-instruments' => 4,
                        'copytrading/first-copy-settings' => 4,
                        'copytrading/amend-copy-settings' => 4,
                        'copytrading/stop-copy-trading' => 4,
                        'copytrading/batch-set-leverage' => 4,
                        // broker
                        'broker/nd/create-subaccount' => 0.25,
                        'broker/nd/delete-subaccount' => 1,
                        'broker/nd/subaccount/apikey' => 0.25,
                        'broker/nd/subaccount/modify-apikey' => 1,
                        'broker/nd/subaccount/delete-apikey' => 1,
                        'broker/nd/set-subaccount-level' => 4,
                        'broker/nd/set-subaccount-fee-rate' => 4,
                        'broker/nd/set-subaccount-assets' => 0.25,
                        'asset/broker/nd/subaccount-deposit-address' => 1,
                        'asset/broker/nd/modify-subaccount-deposit-address' => 5 / 3,
                        'broker/nd/rebate-per-orders' => 36000,
                        'finance/sfp/dcd/quote' => 10,
                        'finance/sfp/dcd/order' => 10,
                        'broker/nd/report-subaccount-ip' => 0.25,
                        'broker/fd/rebate-per-orders' => 36000,
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'taker' => $this->parse_number('0.0015'),
                    'maker' => $this->parse_number('0.0010'),
                ),
                'spot' => array(
                    'taker' => $this->parse_number('0.0015'),
                    'maker' => $this->parse_number('0.0010'),
                ),
                'future' => array(
                    'taker' => $this->parse_number('0.0005'),
                    'maker' => $this->parse_number('0.0002'),
                ),
                'swap' => array(
                    'taker' => $this->parse_number('0.00050'),
                    'maker' => $this->parse_number('0.00020'),
                ),
            ),
            'requiredCredentials' => array(
                'apiKey' => true,
                'secret' => true,
                'password' => true,
            ),
            'exceptions' => array(
                'exact' => array(
                    // Public error codes from 50000-53999
                    // General Class
                    '1' => '\\ccxt\\ExchangeError', // Operation failed
                    '2' => '\\ccxt\\ExchangeError', // Bulk operation partially succeeded
                    '4088' => '\\ccxt\\ManualInteractionNeeded', // array("code":"4088","data":array(),"msg":"You can’t trade or deposit until you’ve verified your identity again. Head to Identity Verification to complete it.")
                    '50000' => '\\ccxt\\BadRequest', // Body can not be empty
                    '50001' => '\\ccxt\\OnMaintenance', // Matching engine upgrading. Please try again later
                    '50002' => '\\ccxt\\BadRequest', // Json data format error
                    '50004' => '\\ccxt\\RequestTimeout', // Endpoint request timeout (does not indicate success or failure of order, please check order status)
                    '50005' => '\\ccxt\\ExchangeNotAvailable', // API is offline or unavailable
                    '50006' => '\\ccxt\\BadRequest', // Invalid Content_Type, please use "application/json" format
                    '50007' => '\\ccxt\\AccountSuspended', // Account blocked
                    '50008' => '\\ccxt\\AuthenticationError', // User does not exist
                    '50009' => '\\ccxt\\AccountSuspended', // Account is suspended due to ongoing liquidation
                    '50010' => '\\ccxt\\ExchangeError', // User ID can not be empty
                    '50011' => '\\ccxt\\RateLimitExceeded', // Request too frequent
                    '50012' => '\\ccxt\\ExchangeError', // Account status invalid
                    '50013' => '\\ccxt\\ExchangeNotAvailable', // System is busy, please try again later
                    '50014' => '\\ccxt\\BadRequest', // Parameter {0} can not be empty
                    '50015' => '\\ccxt\\ExchangeError', // Either parameter {0} or {1} is required
                    '50016' => '\\ccxt\\ExchangeError', // Parameter {0} does not match parameter {1}
                    '50017' => '\\ccxt\\ExchangeError', // The position is frozen due to ADL. Operation restricted
                    '50018' => '\\ccxt\\ExchangeError', // Currency {0} is frozen due to ADL. Operation restricted
                    '50019' => '\\ccxt\\ExchangeError', // The account is frozen due to ADL. Operation restricted
                    '50020' => '\\ccxt\\ExchangeError', // The position is frozen due to liquidation. Operation restricted
                    '50021' => '\\ccxt\\ExchangeError', // Currency {0} is frozen due to liquidation. Operation restricted
                    '50022' => '\\ccxt\\ExchangeError', // The account is frozen due to liquidation. Operation restricted
                    '50023' => '\\ccxt\\ExchangeError', // Funding fee frozen. Operation restricted
                    '50024' => '\\ccxt\\BadRequest', // Parameter {0} and {1} can not exist at the same time
                    '50025' => '\\ccxt\\ExchangeError', // Parameter {0} count exceeds the limit {1}
                    '50026' => '\\ccxt\\ExchangeNotAvailable', // System error, please try again later.
                    '50027' => '\\ccxt\\PermissionDenied', // The account is restricted from trading
                    '50028' => '\\ccxt\\ExchangeError', // Unable to take the order, please reach out to support center for details
                    '50044' => '\\ccxt\\BadRequest', // Must select one broker type
                    '50061' => '\\ccxt\\ExchangeError', // You've reached the maximum order rate limit for this account.
                    '50062' => '\\ccxt\\ExchangeError', // This feature is currently unavailable.
                    // API Class
                    '50100' => '\\ccxt\\ExchangeError', // API frozen, please contact customer service
                    '50101' => '\\ccxt\\AuthenticationError', // Broker id of APIKey does not match current environment
                    '50102' => '\\ccxt\\InvalidNonce', // Timestamp request expired
                    '50103' => '\\ccxt\\AuthenticationError', // Request header "OK_ACCESS_KEY" can not be empty
                    '50104' => '\\ccxt\\AuthenticationError', // Request header "OK_ACCESS_PASSPHRASE" can not be empty
                    '50105' => '\\ccxt\\AuthenticationError', // Request header "OK_ACCESS_PASSPHRASE" incorrect
                    '50106' => '\\ccxt\\AuthenticationError', // Request header "OK_ACCESS_SIGN" can not be empty
                    '50107' => '\\ccxt\\AuthenticationError', // Request header "OK_ACCESS_TIMESTAMP" can not be empty
                    '50108' => '\\ccxt\\ExchangeError', // Exchange ID does not exist
                    '50109' => '\\ccxt\\ExchangeError', // Exchange domain does not exist
                    '50110' => '\\ccxt\\PermissionDenied', // Invalid IP
                    '50111' => '\\ccxt\\AuthenticationError', // Invalid OK_ACCESS_KEY
                    '50112' => '\\ccxt\\AuthenticationError', // Invalid OK_ACCESS_TIMESTAMP
                    '50113' => '\\ccxt\\AuthenticationError', // Invalid signature
                    '50114' => '\\ccxt\\AuthenticationError', // Invalid authorization
                    '50115' => '\\ccxt\\BadRequest', // Invalid request method
                    // Trade Class
                    '51000' => '\\ccxt\\BadRequest', // Parameter {0} error
                    '51001' => '\\ccxt\\BadSymbol', // Instrument ID does not exist
                    '51002' => '\\ccxt\\BadSymbol', // Instrument ID does not match underlying index
                    '51003' => '\\ccxt\\BadRequest', // Either client order ID or order ID is required
                    '51004' => '\\ccxt\\InvalidOrder', // Order amount exceeds current tier limit
                    '51005' => '\\ccxt\\InvalidOrder', // Order amount exceeds the limit
                    '51006' => '\\ccxt\\InvalidOrder', // Order price out of the limit
                    '51007' => '\\ccxt\\InvalidOrder', // Order placement failed. Order amount should be at least 1 contract (showing up when placing an order with less than 1 contract)
                    '51008' => '\\ccxt\\InsufficientFunds', // Order placement failed due to insufficient balance or margin
                    '51009' => '\\ccxt\\AccountSuspended', // Order placement function is blocked by the platform
                    '51010' => '\\ccxt\\AccountNotEnabled', // Account level too low array("code":"1","data":[array("clOrdId":"uJrfGFth9F","ordId":"","sCode":"51010","sMsg":"The current account mode does not support this API interface. ","tag":"")],"msg":"Operation failed.")
                    '51011' => '\\ccxt\\InvalidOrder', // Duplicated order ID
                    '51012' => '\\ccxt\\BadSymbol', // Token does not exist
                    '51014' => '\\ccxt\\BadSymbol', // Index does not exist
                    '51015' => '\\ccxt\\BadSymbol', // Instrument ID does not match instrument type
                    '51016' => '\\ccxt\\InvalidOrder', // Duplicated client order ID
                    '51017' => '\\ccxt\\ExchangeError', // Borrow amount exceeds the limit
                    '51018' => '\\ccxt\\ExchangeError', // User with option account can not hold net short positions
                    '51019' => '\\ccxt\\ExchangeError', // No net long positions can be held under isolated margin mode in options
                    '51020' => '\\ccxt\\InvalidOrder', // Order amount should be greater than the min available amount
                    '51021' => '\\ccxt\\ContractUnavailable', // Contract to be listed
                    '51022' => '\\ccxt\\ContractUnavailable', // Contract suspended
                    '51023' => '\\ccxt\\ExchangeError', // Position does not exist
                    '51024' => '\\ccxt\\AccountSuspended', // Unified accountblocked
                    '51025' => '\\ccxt\\ExchangeError', // Order count exceeds the limit
                    '51026' => '\\ccxt\\BadSymbol', // Instrument type does not match underlying index
                    '51027' => '\\ccxt\\ContractUnavailable', // Contract expired
                    '51028' => '\\ccxt\\ContractUnavailable', // Contract under delivery
                    '51029' => '\\ccxt\\ContractUnavailable', // Contract is being settled
                    '51030' => '\\ccxt\\ContractUnavailable', // Funding fee is being settled
                    '51031' => '\\ccxt\\InvalidOrder', // This order price is not within the closing price range
                    '51046' => '\\ccxt\\InvalidOrder', // The take profit trigger price must be higher than the order price
                    '51047' => '\\ccxt\\InvalidOrder', // The stop loss trigger price must be lower than the order price
                    '51051' => '\\ccxt\\InvalidOrder', // Your SL price should be lower than the primary order price
                    '51072' => '\\ccxt\\InvalidOrder', // As a spot lead trader, you need to set tdMode to 'spot_isolated' when configured buying lead trade pairs
                    '51073' => '\\ccxt\\InvalidOrder', // As a spot lead trader, you need to use '/copytrading/close-subposition' for selling assets through lead trades
                    '51074' => '\\ccxt\\InvalidOrder', // Only the tdMode for lead trade pairs configured by spot lead traders can be set to 'spot_isolated'
                    '51090' => '\\ccxt\\InvalidOrder', // You can't modify the amount of an SL order placed with a TP limit order.
                    '51091' => '\\ccxt\\InvalidOrder', // All TP orders in one order must be of the same type.
                    '51092' => '\\ccxt\\InvalidOrder', // TP order prices (is_array(one order must be different.
                    '51093' => '\\ccxt\\InvalidOrder', // TP limit order prices (tpOrdPx) && array_key_exists(tpOrdPx), one order must be different.
                    '51093' => '\\ccxt\\InvalidOrder', // TP limit order prices (tpOrdPx)) in one order can't be –1 (market price).
                    '51094' => '\\ccxt\\InvalidOrder', // You can't place TP limit orders in spot, margin, or options trading.
                    '51095' => '\\ccxt\\InvalidOrder', // To place TP limit orders at this endpoint, you must place an SL order at the same time.
                    '51096' => '\\ccxt\\InvalidOrder', // cxlOnClosePos needs to be true to place a TP limit order
                    '51098' => '\\ccxt\\InvalidOrder', // You can't add a new TP order to an SL order placed with a TP limit order.
                    '51099' => '\\ccxt\\InvalidOrder', // You can't place TP limit orders lead trader.
                    '51100' => '\\ccxt\\InvalidOrder', // Trading amount does not meet the min tradable amount
                    '51101' => '\\ccxt\\InvalidOrder', // Entered amount exceeds the max pending order amount (Cont) per transaction
                    '51102' => '\\ccxt\\InvalidOrder', // Entered amount exceeds the max pending count
                    '51103' => '\\ccxt\\InvalidOrder', // Entered amount exceeds the max pending order count of the underlying asset
                    '51104' => '\\ccxt\\InvalidOrder', // Entered amount exceeds the max pending order amount (Cont) of the underlying asset
                    '51105' => '\\ccxt\\InvalidOrder', // Entered amount exceeds the max order amount (Cont) of the contract
                    '51106' => '\\ccxt\\InvalidOrder', // Entered amount exceeds the max order amount (Cont) of the underlying asset
                    '51107' => '\\ccxt\\InvalidOrder', // Entered amount exceeds the max holding amount (Cont)
                    '51108' => '\\ccxt\\InvalidOrder', // Positions exceed the limit for closing out with the market price
                    '51109' => '\\ccxt\\InvalidOrder', // No available offer
                    '51110' => '\\ccxt\\InvalidOrder', // You can only place a limit order after Call Auction has started
                    '51111' => '\\ccxt\\BadRequest', // Maximum {0} orders can be placed in bulk
                    '51112' => '\\ccxt\\InvalidOrder', // Close order size exceeds your available size
                    '51113' => '\\ccxt\\RateLimitExceeded', // Market-price liquidation requests too frequent
                    '51115' => '\\ccxt\\InvalidOrder', // Cancel all pending close-orders before liquidation
                    '51116' => '\\ccxt\\InvalidOrder', // Order price or trigger price exceeds {0}
                    '51117' => '\\ccxt\\InvalidOrder', // Pending close-orders count exceeds limit
                    '51118' => '\\ccxt\\InvalidOrder', // Total amount should exceed the min amount per order
                    '51119' => '\\ccxt\\InsufficientFunds', // Order placement failed due to insufficient balance
                    '51120' => '\\ccxt\\InvalidOrder', // Order quantity is less than {0}, please try again
                    '51121' => '\\ccxt\\InvalidOrder', // Order count should be the integer multiples of the lot size
                    '51122' => '\\ccxt\\InvalidOrder', // Order price should be higher than the min price {0}
                    '51124' => '\\ccxt\\InvalidOrder', // You can only place limit orders during call auction
                    '51125' => '\\ccxt\\InvalidOrder', // Currently there are reduce . reverse position pending orders in margin trading. Please cancel all reduce . reverse position pending orders and continue
                    '51126' => '\\ccxt\\InvalidOrder', // Currently there are reduce only pending orders in margin trading.Please cancel all reduce only pending orders and continue
                    '51127' => '\\ccxt\\InsufficientFunds', // Available balance is 0
                    '51128' => '\\ccxt\\InvalidOrder', // Multi-currency margin account can not do cross-margin trading
                    '51129' => '\\ccxt\\InvalidOrder', // The value of the position and buy order has reached the position limit, and no further buying is allowed
                    '51130' => '\\ccxt\\BadSymbol', // Fixed margin currency error
                    '51131' => '\\ccxt\\InsufficientFunds', // Insufficient balance
                    '51132' => '\\ccxt\\InvalidOrder', // Your position amount is negative and less than the minimum trading amount
                    '51133' => '\\ccxt\\InvalidOrder', // Reduce-only feature is unavailable for the spot transactions by multi-currency margin account
                    '51134' => '\\ccxt\\InvalidOrder', // Closing failed. Please check your holdings and pending orders
                    '51135' => '\\ccxt\\InvalidOrder', // Your closing price has triggered the limit price, and the max buy price is {0}
                    '51136' => '\\ccxt\\InvalidOrder', // Your closing price has triggered the limit price, and the min sell price is {0}
                    '51137' => '\\ccxt\\InvalidOrder', // Your opening price has triggered the limit price, and the max buy price is {0}
                    '51138' => '\\ccxt\\InvalidOrder', // Your opening price has triggered the limit price, and the min sell price is {0}
                    '51139' => '\\ccxt\\InvalidOrder', // Reduce-only feature is unavailable for the spot transactions by simple account
                    '51155' => '\\ccxt\\RestrictedLocation', // array("code":"1","data":[array("clOrdId":"e847xxx","ordId":"","sCode":"51155","sMsg":"You can't trade this pair or borrow this crypto due to local compliance restrictions. ","tag":"e847xxx","ts":"1753979177157")],"inTime":"1753979177157408","msg":"All operations failed","outTime":"1753979177157874")
                    '51156' => '\\ccxt\\BadRequest', // You're leading trades in long/short mode and can't use this API endpoint to close positions
                    '51159' => '\\ccxt\\BadRequest', // You're leading trades in buy/sell mode. If you want to place orders using this API endpoint, the orders must be in the same direction existing positions and open orders.
                    '51162' => '\\ccxt\\InvalidOrder', // You have {instrument} open orders. Cancel these orders and try again
                    '51163' => '\\ccxt\\InvalidOrder', // You hold {instrument} positions. Close these positions and try again
                    '51166' => '\\ccxt\\InvalidOrder', // Currently, we don't support leading trades with this instrument
                    '51174' => '\\ccxt\\InvalidOrder', // The number of {param0} pending orders reached the upper limit of {param1} (orders).
                    '51185' => '\\ccxt\\InvalidOrder', // The maximum value allowed per order is {maxOrderValue} USD
                    '51201' => '\\ccxt\\InvalidOrder', // Value of per market order cannot exceed 100,000 USDT
                    '51202' => '\\ccxt\\InvalidOrder', // Market - order amount exceeds the max amount
                    '51203' => '\\ccxt\\InvalidOrder', // Order amount exceeds the limit {0}
                    '51204' => '\\ccxt\\InvalidOrder', // The price for the limit order can not be empty
                    '51205' => '\\ccxt\\InvalidOrder', // Reduce-Only is not available
                    '51250' => '\\ccxt\\InvalidOrder', // Algo order price is out of the available range
                    '51251' => '\\ccxt\\InvalidOrder', // Algo order type error (when user place an iceberg order)
                    '51252' => '\\ccxt\\InvalidOrder', // Algo order price is out of the available range
                    '51253' => '\\ccxt\\InvalidOrder', // Average amount exceeds the limit of per iceberg order
                    '51254' => '\\ccxt\\InvalidOrder', // Iceberg average amount error (when user place an iceberg order)
                    '51255' => '\\ccxt\\InvalidOrder', // Limit of per iceberg order => Total amount/1000 < x <= Total amount
                    '51256' => '\\ccxt\\InvalidOrder', // Iceberg order price variance error
                    '51257' => '\\ccxt\\InvalidOrder', // Trail order callback rate error
                    '51258' => '\\ccxt\\InvalidOrder', // Trail - order placement failed. The trigger price of a sell order should be higher than the last transaction price
                    '51259' => '\\ccxt\\InvalidOrder', // Trail - order placement failed. The trigger price of a buy order should be lower than the last transaction price
                    '51260' => '\\ccxt\\InvalidOrder', // Maximum {0} pending trail - orders can be held at the same time
                    '51261' => '\\ccxt\\InvalidOrder', // Each user can hold up to {0} pending stop - orders at the same time
                    '51262' => '\\ccxt\\InvalidOrder', // Maximum {0} pending iceberg orders can be held at the same time
                    '51263' => '\\ccxt\\InvalidOrder', // Maximum {0} pending time-weighted orders can be held at the same time
                    '51264' => '\\ccxt\\InvalidOrder', // Average amount exceeds the limit of per time-weighted order
                    '51265' => '\\ccxt\\InvalidOrder', // Time-weighted order limit error
                    '51267' => '\\ccxt\\InvalidOrder', // Time-weighted order strategy initiative rate error
                    '51268' => '\\ccxt\\InvalidOrder', // Time-weighted order strategy initiative range error
                    '51269' => '\\ccxt\\InvalidOrder', // Time-weighted order interval error, the interval should be {0}<= x<={1}
                    '51270' => '\\ccxt\\InvalidOrder', // The limit of time-weighted order price variance is 0 < x <= 1%
                    '51271' => '\\ccxt\\InvalidOrder', // Sweep ratio should be 0 < x <= 100%
                    '51272' => '\\ccxt\\InvalidOrder', // Price variance should be 0 < x <= 1%
                    '51273' => '\\ccxt\\InvalidOrder', // Total amount should be more than {0}
                    '51274' => '\\ccxt\\InvalidOrder', // Total quantity of time-weighted order must be larger than single order limit
                    '51275' => '\\ccxt\\InvalidOrder', // The amount of single stop-market order can not exceed the upper limit
                    '51276' => '\\ccxt\\InvalidOrder', // Stop - Market orders cannot specify a price
                    '51277' => '\\ccxt\\InvalidOrder', // TP trigger price can not be higher than the last price
                    '51278' => '\\ccxt\\InvalidOrder', // SL trigger price can not be lower than the last price
                    '51279' => '\\ccxt\\InvalidOrder', // TP trigger price can not be lower than the last price
                    '51280' => '\\ccxt\\InvalidOrder', // SL trigger price can not be higher than the last price
                    '51321' => '\\ccxt\\InvalidOrder', // You're leading trades. Currently, we don't support leading trades with arbitrage, iceberg, or TWAP bots
                    '51322' => '\\ccxt\\InvalidOrder', // You're leading trades that have been filled at market price. We've canceled your open stop orders to close your positions
                    '51323' => '\\ccxt\\BadRequest', // You're already leading trades with take profit or stop loss settings. Cancel your existing stop orders to proceed
                    '51324' => '\\ccxt\\BadRequest', // As a lead trader, you hold positions in {instrument}. To close your positions, place orders in the amount that equals the available amount for closing
                    '51325' => '\\ccxt\\InvalidOrder', // As a lead trader, you must use market price when placing stop orders
                    '51327' => '\\ccxt\\InvalidOrder', // closeFraction is only available for futures and perpetual swaps
                    '51328' => '\\ccxt\\InvalidOrder', // closeFraction is only available for reduceOnly orders
                    '51329' => '\\ccxt\\InvalidOrder', // closeFraction is only available in NET mode
                    '51330' => '\\ccxt\\InvalidOrder', // closeFraction is only available for stop market orders
                    '51400' => '\\ccxt\\OrderNotFound', // Cancellation failed order does not exist
                    '51401' => '\\ccxt\\OrderNotFound', // Cancellation failed order is already canceled
                    '51402' => '\\ccxt\\OrderNotFound', // Cancellation failed order is already completed
                    '51403' => '\\ccxt\\InvalidOrder', // Cancellation failed order type does not support cancellation
                    '51404' => '\\ccxt\\InvalidOrder', // Order cancellation unavailable during the second phase of call auction
                    '51405' => '\\ccxt\\ExchangeError', // Cancellation failed do not have any pending orders
                    '51406' => '\\ccxt\\ExchangeError', // Canceled - order count exceeds the limit {0}
                    '51407' => '\\ccxt\\BadRequest', // Either order ID or client order ID is required
                    '51408' => '\\ccxt\\ExchangeError', // Pair ID or name does not match the order info
                    '51409' => '\\ccxt\\ExchangeError', // Either pair ID or pair name ID is required
                    '51410' => '\\ccxt\\CancelPending', // Cancellation failed order is already under cancelling status
                    '51500' => '\\ccxt\\ExchangeError', // Either order price or amount is required
                    '51501' => '\\ccxt\\ExchangeError', // Maximum {0} orders can be modified
                    '51502' => '\\ccxt\\InsufficientFunds', // Order modification failed for insufficient margin or balance
                    '51503' => '\\ccxt\\ExchangeError', // Order modification failed order does not exist
                    '51506' => '\\ccxt\\ExchangeError', // Order modification unavailable for the order type
                    '51508' => '\\ccxt\\ExchangeError', // Orders are not allowed to be modified during the call auction
                    '51509' => '\\ccxt\\ExchangeError', // Modification failed order has been canceled
                    '51510' => '\\ccxt\\ExchangeError', // Modification failed order has been completed
                    '51511' => '\\ccxt\\ExchangeError', // Modification failed order price did not meet the requirement for Post Only
                    '51600' => '\\ccxt\\ExchangeError', // Status not found
                    '51601' => '\\ccxt\\ExchangeError', // Order status and order ID cannot exist at the same time
                    '51602' => '\\ccxt\\ExchangeError', // Either order status or order ID is required
                    '51603' => '\\ccxt\\OrderNotFound', // Order does not exist
                    '51732' => '\\ccxt\\AuthenticationError', // Required user KYC level not met
                    '51733' => '\\ccxt\\AuthenticationError', // User is under risk control
                    '51734' => '\\ccxt\\AuthenticationError', // User KYC Country is not supported
                    '51735' => '\\ccxt\\ExchangeError', // Sub-account is not supported
                    '51736' => '\\ccxt\\InsufficientFunds', // Insufficient {ccy} balance
                    // Data class
                    '52000' => '\\ccxt\\ExchangeError', // No updates
                    // SPOT/MARGIN error codes 54000-54999
                    '54000' => '\\ccxt\\ExchangeError', // Margin transactions unavailable
                    '54001' => '\\ccxt\\ExchangeError', // Only Multi-currency margin account can be set to borrow coins automatically
                    '54008' => '\\ccxt\\InvalidOrder', // This operation is disabled by the 'mass cancel order' endpoint. Please enable it using this endpoint.
                    '54009' => '\\ccxt\\InvalidOrder', // The range of {param0} should be [{param1}, {param2}].
                    '54011' => '\\ccxt\\InvalidOrder', // 200 Pre-market trading contracts are only allowed to reduce the number of positions within 1 hour before delivery. Please modify or cancel the order.
                    '54072' => '\\ccxt\\ExchangeError', // This contract is currently view-only and not tradable.
                    '54073' => '\\ccxt\\BadRequest', // Couldn’t place order, as {param0} is at risk of depegging. Switch settlement currencies and try again.
                    '54074' => '\\ccxt\\ExchangeError', // Your settings failed have positions, bot or open orders for USD contracts.
                    // Trading bot Error Code from 55100 to 55999
                    '55100' => '\\ccxt\\InvalidOrder', // Take fmod(profit, should) be within the range of {parameter1}-{parameter2}
                    '55101' => '\\ccxt\\InvalidOrder', // Stop fmod(loss, should) be within the range of {parameter1}-{parameter2}
                    '55102' => '\\ccxt\\InvalidOrder', // Take fmod(profit, should) be greater than the current bot’s PnL%
                    '55103' => '\\ccxt\\InvalidOrder', // Stop fmod(loss, should) be less than the current bot’s PnL%
                    '55104' => '\\ccxt\\InvalidOrder', // Only futures grid supports take profit or stop loss based on profit percentage
                    '55111' => '\\ccxt\\InvalidOrder', // This signal name is in use, please try a new name
                    '55112' => '\\ccxt\\InvalidOrder', // This signal does not exist
                    '55113' => '\\ccxt\\InvalidOrder', // Create signal strategies with leverage greater than the maximum leverage of the instruments
                    // FUNDING error codes 58000-58999
                    '58000' => '\\ccxt\\ExchangeError', // Account type {0} does not supported when getting the sub-account balance
                    '58001' => '\\ccxt\\AuthenticationError', // Incorrect trade password
                    '58002' => '\\ccxt\\PermissionDenied', // Please activate Savings Account first
                    '58003' => '\\ccxt\\ExchangeError', // Currency type is not supported by Savings Account
                    '58004' => '\\ccxt\\AccountSuspended', // Account blocked (transfer & withdrawal endpoint => either end of the account does not authorize the transfer)
                    '58005' => '\\ccxt\\ExchangeError', // The redeemed amount must be no greater than {0}
                    '58006' => '\\ccxt\\ExchangeError', // Service unavailable for token {0}
                    '58007' => '\\ccxt\\ExchangeError', // Abnormal Assets interface. Please try again later
                    '58100' => '\\ccxt\\ExchangeError', // The trading product triggers risk control, and the platform has suspended the fund transfer-out function with related users. Please wait patiently
                    '58101' => '\\ccxt\\AccountSuspended', // Transfer suspended (transfer endpoint => either end of the account does not authorize the transfer)
                    '58102' => '\\ccxt\\RateLimitExceeded', // Too frequent transfer (transfer too frequently)
                    '58103' => '\\ccxt\\ExchangeError', // Parent account user id does not match sub-account user id
                    '58104' => '\\ccxt\\ExchangeError', // Since your P2P transaction is abnormal, you are restricted from making fund transfers. Please contact customer support to remove the restriction
                    '58105' => '\\ccxt\\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' => '\\ccxt\\ExchangeError', // Please enable the account for spot contract
                    '58107' => '\\ccxt\\ExchangeError', // Please enable the account for futures contract
                    '58108' => '\\ccxt\\ExchangeError', // Please enable the account for option contract
                    '58109' => '\\ccxt\\ExchangeError', // Please enable the account for swap contract
                    '58110' => '\\ccxt\\ExchangeError', // The contract triggers risk control, and the platform has suspended the fund transfer function of it. Please wait patiently
                    '58111' => '\\ccxt\\ExchangeError', // Funds transfer unavailable perpetual contract is charging the funding fee. Please try again later
                    '58112' => '\\ccxt\\ExchangeError', // Your fund transfer failed. Please try again later
                    '58114' => '\\ccxt\\ExchangeError', // Transfer amount must be more than 0
                    '58115' => '\\ccxt\\ExchangeError', // Sub-account does not exist
                    '58116' => '\\ccxt\\ExchangeError', // Transfer amount exceeds the limit
                    '58117' => '\\ccxt\\ExchangeError', // Account assets are abnormal, please deal with negative assets before transferring
                    '58125' => '\\ccxt\\BadRequest', // Non-tradable assets can only be transferred from sub-accounts to main accounts
                    '58126' => '\\ccxt\\BadRequest', // Non-tradable assets can only be transferred between funding accounts
                    '58127' => '\\ccxt\\BadRequest', // Main account API Key does not support current transfer 'type' parameter. Please refer to the API documentation.
                    '58128' => '\\ccxt\\BadRequest', // Sub-account API Key does not support current transfer 'type' parameter. Please refer to the API documentation.
                    '58200' => '\\ccxt\\ExchangeError', // Withdrawal from {0} to {1} is unavailable for this currency
                    '58201' => '\\ccxt\\ExchangeError', // Withdrawal amount exceeds the daily limit
                    '58202' => '\\ccxt\\ExchangeError', // The minimum withdrawal amount for NEO is 1, and the amount must be an integer
                    '58203' => '\\ccxt\\InvalidAddress', // Please add a withdrawal address
                    '58204' => '\\ccxt\\AccountSuspended', // Withdrawal suspended
                    '58205' => '\\ccxt\\ExchangeError', // Withdrawal amount exceeds the upper limit
                    '58206' => '\\ccxt\\ExchangeError', // Withdrawal amount is lower than the lower limit
                    '58207' => '\\ccxt\\InvalidAddress', // Withdrawal failed due to address error
                    '58208' => '\\ccxt\\ExchangeError', // Withdrawal failed. Please link your email
                    '58209' => '\\ccxt\\ExchangeError', // Withdrawal failed. Withdraw feature is not available for sub-accounts
                    '58210' => '\\ccxt\\ExchangeError', // Withdrawal fee exceeds the upper limit
                    '58211' => '\\ccxt\\ExchangeError', // Withdrawal fee is lower than the lower limit (withdrawal endpoint => incorrect fee)
                    '58212' => '\\ccxt\\ExchangeError', // Withdrawal fee should be {0}% of the withdrawal amount
                    '58213' => '\\ccxt\\AuthenticationError', // Please set trading password before withdrawal
                    '58221' => '\\ccxt\\BadRequest', // Missing label of withdrawal address.
                    '58222' => '\\ccxt\\BadRequest', // Illegal withdrawal address.
                    '58224' => '\\ccxt\\BadRequest', // This type of crypto does not support on-chain withdrawing to OKX addresses. Please withdraw through internal transfers.
                    '58227' => '\\ccxt\\BadRequest', // Withdrawal of non-tradable assets can be withdrawn all at once only
                    '58228' => '\\ccxt\\BadRequest', // Withdrawal of non-tradable assets requires that the API Key must be bound to an IP
                    '58229' => '\\ccxt\\InsufficientFunds', // Insufficient funding account balance to pay fees {fee} USDT
                    '58300' => '\\ccxt\\ExchangeError', // Deposit-address count exceeds the limit
                    '58350' => '\\ccxt\\InsufficientFunds', // Insufficient balance
                    // Account error codes 59000-59999
                    '59000' => '\\ccxt\\ExchangeError', // Your settings failed have positions or open orders
                    '59001' => '\\ccxt\\ExchangeError', // Switching unavailable have borrowings
                    '59100' => '\\ccxt\\ExchangeError', // You have open positions. Please cancel all open positions before changing the leverage
                    '59101' => '\\ccxt\\ExchangeError', // You have pending orders with isolated positions. Please cancel all the pending orders and adjust the leverage
                    '59102' => '\\ccxt\\ExchangeError', // Leverage exceeds the maximum leverage. Please adjust the leverage
                    '59103' => '\\ccxt\\InsufficientFunds', // Leverage is too low and no sufficient margin in your account. Please adjust the leverage
                    '59104' => '\\ccxt\\ExchangeError', // The leverage is too high. The borrowed position has exceeded the maximum position of this leverage. Please adjust the leverage
                    '59105' => '\\ccxt\\ExchangeError', // Leverage can not be less than {0}. Please adjust the leverage
                    '59106' => '\\ccxt\\ExchangeError', // The max available margin corresponding to your order tier is {0}. Please adjust your margin and place a new order
                    '59107' => '\\ccxt\\ExchangeError', // You have pending orders under the service, please modify the leverage after canceling all pending orders
                    '59108' => '\\ccxt\\InsufficientFunds', // Low leverage and insufficient margin, please adjust the leverage
                    '59109' => '\\ccxt\\ExchangeError', // Account equity less than the required margin amount after adjustment. Please adjust the leverage
                    '59128' => '\\ccxt\\InvalidOrder', // As a lead trader, you can't lead trades in {instrument} with leverage higher than {num}
                    '59200' => '\\ccxt\\InsufficientFunds', // Insufficient account balance
                    '59201' => '\\ccxt\\InsufficientFunds', // Negative account balance
                    '59216' => '\\ccxt\\BadRequest', // The position doesn't exist. Please try again
                    '59260' => '\\ccxt\\PermissionDenied', // You are not a spot lead trader yet. Complete the application on our website or app first.
                    '59262' => '\\ccxt\\PermissionDenied', // You are not a contract lead trader yet. Complete the application on our website or app first.
                    '59300' => '\\ccxt\\ExchangeError', // Margin call failed. Position does not exist
                    '59301' => '\\ccxt\\ExchangeError', // Margin adjustment failed for exceeding the max limit
                    '59313' => '\\ccxt\\ExchangeError', // Unable to repay. You haven't borrowed any {ccy} {ccyPair} in Quick margin mode.
                    '59401' => '\\ccxt\\ExchangeError', // Holdings already reached the limit
                    '59410' => '\\ccxt\\OperationRejected', // You can only borrow this crypto if it supports borrowing and borrowing is enabled.
                    '59411' => '\\ccxt\\InsufficientFunds', // Manual borrowing failed. Your account's free margin is insufficient
                    '59412' => '\\ccxt\\OperationRejected', // Manual borrowing failed. The amount exceeds your borrowing limit.
                    '59413' => '\\ccxt\\OperationRejected', // You didn't borrow this crypto. No repayment needed.
                    '59414' => '\\ccxt\\BadRequest', // Manual borrowing failed. The minimum borrowing limit is {param0}.needed.
                    '59500' => '\\ccxt\\ExchangeError', // Only the APIKey of the main account has permission
                    '59501' => '\\ccxt\\ExchangeError', // Only 50 APIKeys can be created per account
                    '59502' => '\\ccxt\\ExchangeError', // Note name cannot be duplicate with the currently created APIKey note name
                    '59503' => '\\ccxt\\ExchangeError', // Each APIKey can bind up to 20 IP addresses
                    '59504' => '\\ccxt\\ExchangeError', // The sub account does not support the withdrawal function
                    '59505' => '\\ccxt\\ExchangeError', // The passphrase format is incorrect
                    '59506' => '\\ccxt\\ExchangeError', // APIKey does not exist
                    '59507' => '\\ccxt\\ExchangeError', // The two accounts involved in a transfer must be two different sub accounts under the same parent account
                    '59508' => '\\ccxt\\AccountSuspended', // The sub account of {0} is suspended
                    '59515' => '\\ccxt\\ExchangeError', // You are currently not on the custody whitelist. Please contact customer service for assistance.
                    '59516' => '\\ccxt\\ExchangeError', // Please create the Copper custody funding account first.
                    '59517' => '\\ccxt\\ExchangeError', // Please create the Komainu custody funding account first.
                    '59518' => '\\ccxt\\ExchangeError', // You can’t create a sub-account using the API; please use the app or web.
                    '59519' => '\\ccxt\\ExchangeError', // You can’t use this function/feature while it's frozen, due to => {freezereason}
                    '59642' => '\\ccxt\\BadRequest', // Lead and copy traders can only use margin-free or single-currency margin account modes
                    '59643' => '\\ccxt\\ExchangeError', // Couldn’t switch account modes’re currently copying spot trades
                    '59683' => '\\ccxt\\ExchangeError', // Set this crypto collateral crypto before selecting it settlement currency.
                    '59684' => '\\ccxt\\BadRequest', // Borrowing isn’t supported for this currency.
                    '59686' => '\\ccxt\\BadRequest', // This crypto can’t be set settlement currency.
                    // WebSocket error Codes from 60000-63999
                    '60001' => '\\ccxt\\AuthenticationError', // "OK_ACCESS_KEY" can not be empty
                    '60002' => '\\ccxt\\AuthenticationError', // "OK_ACCESS_SIGN" can not be empty
                    '60003' => '\\ccxt\\AuthenticationError', // "OK_ACCESS_PASSPHRASE" can not be empty
                    '60004' => '\\ccxt\\AuthenticationError', // Invalid OK_ACCESS_TIMESTAMP
                    '60005' => '\\ccxt\\AuthenticationError', // Invalid OK_ACCESS_KEY
                    '60006' => '\\ccxt\\InvalidNonce', // Timestamp request expired
                    '60007' => '\\ccxt\\AuthenticationError', // Invalid sign
                    '60008' => '\\ccxt\\AuthenticationError', // Login is not supported for public channels
                    '60009' => '\\ccxt\\AuthenticationError', // Login failed
                    '60010' => '\\ccxt\\AuthenticationError', // Already logged in
                    '60011' => '\\ccxt\\AuthenticationError', // Please log in
                    '60012' => '\\ccxt\\BadRequest', // Illegal request
                    '60013' => '\\ccxt\\BadRequest', // Invalid args
                    '60014' => '\\ccxt\\RateLimitExceeded', // Requests too frequent
                    '60015' => '\\ccxt\\NetworkError', // Connection closed was no data transmission in the last 30 seconds
                    '60016' => '\\ccxt\\ExchangeNotAvailable', // Buffer is full, cannot write data
                    '60017' => '\\ccxt\\BadRequest', // Invalid url path
                    '60018' => '\\ccxt\\BadRequest', // The {0} {1} {2} {3} {4} does not exist
                    '60019' => '\\ccxt\\BadRequest', // Invalid op {op}
                    '60020' => '\\ccxt\\ExchangeError',  // APIKey subscription amount exceeds the limit
                    '60021' => '\\ccxt\\AccountNotEnabled',  // This operation does not support multiple accounts login
                    '60022' => '\\ccxt\\AuthenticationError',  // Bulk login partially succeeded
                    '60023' => '\\ccxt\\DDoSProtection',  // Bulk login requests too frequent
                    '60024' => '\\ccxt\\AuthenticationError',  // Wrong passphrase
                    '60025' => '\\ccxt\\ExchangeError',  // Token subscription amount exceeds the limit
                    '60026' => '\\ccxt\\AuthenticationError',  // Batch login by APIKey and token simultaneously is not supported
                    '60027' => '\\ccxt\\ArgumentsRequired',  // Parameter {0} can not be empty
                    '60028' => '\\ccxt\\NotSupported',  // The current operation is not supported by this URL
                    '60029' => '\\ccxt\\AccountNotEnabled',  // Only users who are VIP5 and above in trading fee tier are allowed to subscribe to books-l2-tbt channel
                    '60030' => '\\ccxt\\AccountNotEnabled',  // Only users who are VIP4 and above in trading fee tier are allowed to subscribe to books50-l2-tbt channel
                    '60031' => '\\ccxt\\AuthenticationError', // The WebSocket endpoint does not support multiple account batch login,
                    '60032' => '\\ccxt\\AuthenticationError', // API key doesn't exist,
                    '63999' => '\\ccxt\\ExchangeError', // Internal system error
                    '64000' => '\\ccxt\\BadRequest', // Subscription parameter uly is unavailable anymore, please replace uly with instFamily. More details can refer to => https://www.okx.com/help-center/changes-to-v5-api-websocket-subscription-parameter-and-url,
                    '64001' => '\\ccxt\\BadRequest', // This channel has been migrated to the business URL. Please subscribe using the new URL. More details can refer to => https://www.okx.com/help-center/changes-to-v5-api-websocket-subscription-parameter-and-url,
                    '64002' => '\\ccxt\\BadRequest', // This channel is not supported by business URL. Please use "/private" URL(for private channels), or "/public" URL(for public channels). More details can refer to => https://www.okx.com/help-center/changes-to-v5-api-websocket-subscription-parameter-and-url,
                    '64003' => '\\ccxt\\AccountNotEnabled', // Your trading fee tier doesnt meet the requirement to access this channel
                    '70010' => '\\ccxt\\BadRequest', // Timestamp parameters need to be in Unix timestamp format in milliseconds.
                    '70013' => '\\ccxt\\BadRequest', // endTs needs to be bigger than or equal to beginTs.
                    '70016' => '\\ccxt\\BadRequest', // Please specify your instrument settings for at least one instType.
                    '70060' => '\\ccxt\\BadRequest', // The account doesn’t exist or the position side is incorrect. To and from accounts must be under the same main account.
                    '70061' => '\\ccxt\\BadRequest', // To move position, please enter a position that’s opposite to your current side and is smaller than or equal to your current size.
                    '70062' => '\\ccxt\\BadRequest', // account has reached the maximum number of position transfers allowed per day.
                    '70064' => '\\ccxt\\BadRequest', // Position does not exist.
                    '70065' => '\\ccxt\\BadRequest', // Couldn’t move position. Execution price cannot be determined
                    '70066' => '\\ccxt\\BadRequest', // Moving positions isn't supported in spot mode. Switch to any other account mode and try again.
                    '70067' => '\\ccxt\\BadRequest', // Moving positions isn't supported in margin trading.
                    '1009' => '\\ccxt\\BadRequest',  // Request message exceeds the maximum frame length
                    '4001' => '\\ccxt\\AuthenticationError',  // Login Failed
                    '4002' => '\\ccxt\\BadRequest',  // Invalid Request
                    '4003' => '\\ccxt\\RateLimitExceeded',  // APIKey subscription amount exceeds the limit 100
                    '4004' => '\\ccxt\\NetworkError',  // No data received in 30s
                    '4005' => '\\ccxt\\ExchangeNotAvailable',  // Buffer is full, cannot write data
                    '4006' => '\\ccxt\\BadRequest',  // Abnormal disconnection
                    '4007' => '\\ccxt\\AuthenticationError',  // API key has been updated or deleted. Please reconnect.
                    '4008' => '\\ccxt\\RateLimitExceeded',  // The number of subscribed channels exceeds the maximum limit.
                ),
                'broad' => array(
                    'Internal Server Error' => '\\ccxt\\ExchangeNotAvailable', // array("code":500,"data":array(),"detailMsg":"","error_code":"500","error_message":"Internal Server Error","msg":"Internal Server Error")
                    'server error' => '\\ccxt\\ExchangeNotAvailable', // array("code":500,"data":array(),"detailMsg":"","error_code":"500","error_message":"server error 1236805249","msg":"server error 1236805249")
                ),
            ),
            'httpExceptions' => array(
                '429' => '\\ccxt\\ExchangeNotAvailable', // https://github.com/ccxt/ccxt/issues/9612
            ),
            'precisionMode' => TICK_SIZE,
            'options' => array(
                'sandboxMode' => false,
                'defaultNetwork' => 'ERC20',
                'defaultNetworks' => array(
                    'ETH' => 'ERC20',
                    'BTC' => 'BTC',
                    'USDT' => 'TRC20',
                ),
                'networks' => array(
                    'BTC' => 'Bitcoin',
                    'BTCLN' => 'Lightning',
                    'BTCLIGHTNING' => 'Lightning',
                    'BEP20' => 'BSC',
                    'BRC20' => 'BRC20',
                    'ERC20' => 'ERC20',
                    'TRC20' => 'TRC20',
                    'CRC20' => 'Crypto',
                    'ACA' => 'Acala',
                    'ALGO' => 'Algorand',
                    'APT' => 'Aptos',
                    'SCROLL' => 'Scroll',
                    'ARBONE' => 'Arbitrum One',
                    'AVAXC' => 'Avalanche C-Chain',
                    'AVAXX' => 'Avalanche X-Chain',
                    'BASE' => 'Base',
                    'SUI' => 'SUI',
                    'ZKSYNCERA' => 'zkSync Era',
                    'LINEA' => 'Linea',
                    'AR' => 'Arweave',
                    'ASTR' => 'Astar',
                    'BCH' => 'BitcoinCash',
                    'BSV' => 'Bitcoin SV',
                    'ADA' => 'Cardano',
                    'CSPR' => 'Casper',
                    'CELO' => 'CELO',
                    'XCH' => 'Chia',
                    // 'CHZ' => 'Chiliz', TBD => Chiliz 2.0 Chain vs Chiliz Chain
                    'ATOM' => 'Cosmos',
                    'DGB' => 'Digibyte',
                    'DOGE' => 'Dogecoin',
                    'EGLD' => 'Elrond',
                    'CFX' => 'Conflux', // CFX_EVM is different
                    'EOS' => 'EOS',
                    'CORE' => 'CORE',
                    'ETC' => 'Ethereum Classic',
                    'ETHW' => 'EthereumPow',
                    // 'FTM' => 'Fantom', 'Sonic' TBD
                    'FIL' => 'Filecoin',
                    'ONE' => 'Harmony',
                    'HBAR' => 'Hedera',
                    'ICX' => 'ICON',
                    'ICP' => 'Dfinity',
                    'IOST' => 'IOST',
                    'IOTA' => 'MIOTA',
                    'KLAY' => 'Klaytn',
                    'KSM' => 'Kusama',
                    'LSK' => 'Lisk',
                    'LTC' => 'Litecoin',
                    'METIS' => 'Metis',
                    'MINA' => 'Mina',
                    'GLRM' => 'Moonbeam',
                    'MOVR' => 'Moonriver',
                    'NANO' => 'Nano',
                    'NEAR' => 'NEAR',
                    'NULS' => 'NULS',
                    'OASYS' => 'OASYS',
                    'ONT' => 'Ontology',
                    'OPTIMISM' => 'Optimism',
                    // 'OP' => 'Optimism', or Optimism (V2), TBD
                    'LAT' => 'PlatON',
                    'DOT' => 'Polkadot',
                    'MATIC' => 'Polygon',
                    'RVN' => 'Ravencoin',
                    'XRP' => 'Ripple',
                    'SC' => 'Siacoin',
                    'SOL' => 'Solana',
                    'STX' => 'l-Stacks',
                    'XLM' => 'Stellar Lumens',
                    'XTZ' => 'Tezos',
                    'TON' => 'TON',
                    'THETA' => 'Theta',
                    'WAX' => 'Wax',
                    'ZIL' => 'Zilliqa',
                    // non-supported known network => CRP. KAVA, TAIKO, BOB, GNO, BLAST, RSK, SEI, MANTLE, HYPE, RUNE, OSMO, XIN, WEMIX, HT, FSN, NEO, TLOS, CANTO, SCRT, AURORA, XMR
                    // others:
                    // "OKTC",
                    // "X Layer",
                    // "Polygon (Bridged)",
                    // "BTCK-OKTC",
                    // "ETHK-OKTC",
                    // "Starknet",
                    // "LTCK-OKTC",
                    // "XRPK-OKTC",
                    // "BCHK-OKTC",
                    // "ETCK-OKTC",
                    // "Endurance Smart Chain",
                    // "Berachain",
                    // "CELO-TOKEN",
                    // "CFX_EVM",
                    // "Cortex",
                    // "DAIK-OKTC",
                    // "Dora Vota Mainnet",
                    // "DOTK-OKTC",
                    // "DYDX",
                    // "AELF",
                    // "Enjin Relay Chain",
                    // "FEVM",
                    // "FILK-OKTC",
                    // "Flare",
                    // "Gravity Alpha Mainnet",
                    // "INJ",
                    // "Story",
                    // "LINKK-OKTC",
                    // "Terra",
                    // "Terra Classic",
                    // "Terra Classic (USTC)",
                    // "MERLIN Network",
                    // "Layer 3",
                    // "PI",
                    // "Ronin",
                    // "Quantum",
                    // "SHIBK-OKTC",
                    // "SUSHIK-OKTC",
                    // "Celestia",
                    // "TRXK-OKTC",
                    // "UNIK-OKTC",
                    // "Venom",
                    // "WBTCK-OKTC",
                    // "ZetaChain",
                ),
                'fetchOpenInterestHistory' => array(
                    'timeframes' => array(
                        '5m' => '5m',
                        '1h' => '1H',
                        '8h' => '8H',
                        '1d' => '1D',
                        '5M' => '5m',
                        '1H' => '1H',
                        '8H' => '8H',
                        '1D' => '1D',
                    ),
                ),
                'fetchOHLCV' => array(
                    // 'type' => 'Candles', // Candles or HistoryCandles, IndexCandles, MarkPriceCandles
                    'timezone' => 'UTC', // UTC, HK
                ),
                'fetchPositions' => array(
                    'method' => 'privateGetAccountPositions', // privateGetAccountPositions or privateGetAccountPositionsHistory
                ),
                'createOrder' => 'privatePostTradeBatchOrders', // or 'privatePostTradeOrder' or 'privatePostTradeOrderAlgo'
                'createMarketBuyOrderRequiresPrice' => false,
                'fetchMarkets' => array(
                    'types' => array( 'spot', 'future', 'swap', 'option' ), // spot, future, swap, option
                ),
                'timeDifference' => 0, // the difference between system clock and exchange server clock
                'adjustForTimeDifference' => false, // controls the adjustment logic upon instantiation
                'defaultType' => 'spot', // 'funding', 'spot', 'margin', 'future', 'swap', 'option'
                // 'fetchBalance' => array(
                //     'type' => 'spot', // 'funding', 'trading', 'spot'
                // ),
                'fetchLedger' => array(
                    'method' => 'privateGetAccountBills', // privateGetAccountBills, privateGetAccountBillsArchive, privateGetAssetBills
                ),
                // 6 => Funding account, 18 => Trading account
                'fetchOrder' => array(
                    'method' => 'privateGetTradeOrder', // privateGetTradeOrdersAlgoHistory
                ),
                'fetchOpenOrders' => array(
                    'method' => 'privateGetTradeOrdersPending', // privateGetTradeOrdersAlgoPending
                ),
                'cancelOrders' => array(
                    'method' => 'privatePostTradeCancelBatchOrders', // privatePostTradeCancelAlgos
                ),
                'fetchCanceledOrders' => array(
                    'method' => 'privateGetTradeOrdersHistory', // privateGetTradeOrdersAlgoHistory
                ),
                'fetchClosedOrders' => array(
                    'method' => 'privateGetTradeOrdersHistory', // privateGetTradeOrdersAlgoHistory
                ),
                'withdraw' => array(
                    // a funding password credential is required by the exchange for the
                    // withdraw call (not to be confused with the api password credential)
                    'password' => null,
                    'pwd' => null, // password or pwd both work
                ),
                'algoOrderTypes' => array(
                    'conditional' => true,
                    'trigger' => true,
                    'oco' => true,
                    'move_order_stop' => true,
                    'iceberg' => true,
                    'twap' => true,
                ),
                'accountsByType' => array(
                    'funding' => '6',
                    'trading' => '18', // unified trading account
                    'spot' => '18',
                    'future' => '18',
                    'futures' => '18',
                    'margin' => '18',
                    'swap' => '18',
                    'option' => '18',
                ),
                'accountsById' => array(
                    '6' => 'funding',
                    '18' => 'trading', // unified trading account
                ),
                'exchangeType' => array(
                    'spot' => 'SPOT',
                    'margin' => 'MARGIN',
                    'swap' => 'SWAP',
                    'future' => 'FUTURES',
                    'futures' => 'FUTURES', // deprecated
                    'option' => 'OPTION',
                    'SPOT' => 'SPOT',
                    'MARGIN' => 'MARGIN',
                    'SWAP' => 'SWAP',
                    'FUTURES' => 'FUTURES',
                    'OPTION' => 'OPTION',
                ),
                'brokerId' => '6b9ad766b55dBCDE',
            ),
            'features' => array(
                'default' => array(
                    'sandbox' => true,
                    'createOrder' => array(
                        'marginMode' => true,
                        'triggerPrice' => true,
                        'triggerPriceType' => array(
                            'last' => true,
                            'mark' => true,
                            'index' => true,
                        ),
                        'triggerDirection' => false,
                        'stopLossPrice' => true,
                        'takeProfitPrice' => true,
                        'attachedStopLossTakeProfit' => array(
                            'triggerPriceType' => array(
                                'last' => true,
                                'mark' => true,
                                'index' => true,
                            ),
                            'price' => true,
                        ),
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => true,
                            'GTD' => false,
                        ),
                        'hedged' => true,
                        'trailing' => true,
                        'iceberg' => true, // todo implement
                        'leverage' => false,
                        'selfTradePrevention' => true, // todo implement
                        'marketBuyByCost' => true,
                        'marketBuyRequiresPrice' => false,
                    ),
                    'createOrders' => array(
                        'max' => 20,
                    ),
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'daysBack' => 90,
                        'limit' => 100,
                        'untilDays' => 10000,
                        'symbolRequired' => false,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => true,
                        'trailing' => true,
                        'symbolRequired' => true,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => 100,
                        'trigger' => true,
                        'trailing' => true,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => null, // not supported
                    'fetchClosedOrders' => array(
                        'marginMode' => false,
                        'limit' => 100,
                        'daysBack' => 90, // 3 months
                        'daysBackCanceled' => 1 / 12, // 2 hour
                        'untilDays' => null,
                        'trigger' => true,
                        'trailing' => true,
                        'symbolRequired' => false,
                    ),
                    'fetchOHLCV' => array(
                        'limit' => 300,
                        'historical' => 100,
                    ),
                ),
                'spot' => array(
                    'extends' => 'default',
                    'fetchCurrencies' => array(
                        'private' => true,
                    ),
                ),
                'swap' => array(
                    'linear' => array(
                        'extends' => 'default',
                    ),
                    'inverse' => array(
                        'extends' => 'default',
                    ),
                ),
                'future' => array(
                    'linear' => array(
                        'extends' => 'default',
                    ),
                    'inverse' => array(
                        'extends' => 'default',
                    ),
                ),
            ),
            'commonCurrencies' => array(
                // the exchange refers to ERC20 version of Aeternity (AEToken)
                'AE' => 'AET', // https://github.com/ccxt/ccxt/issues/4981
                'WIN' => 'WINTOKEN', // https://github.com/ccxt/ccxt/issues/5701
            ),
        ));
    }

    public function handle_market_type_and_params(string $methodName, ?array $market = null, $params = array (), $defaultValue = null): mixed {
        $instType = $this->safe_string($params, 'instType');
        $params = $this->omit($params, 'instType');
        $type = $this->safe_string($params, 'type');
        if (($type === null) && ($instType !== null)) {
            $params['type'] = $instType;
        }
        return parent::handle_market_type_and_params($methodName, $market, $params, $defaultValue);
    }

    public function convert_to_instrument_type($type) {
        $exchangeTypes = $this->safe_dict($this->options, 'exchangeType', array());
        return $this->safe_string($exchangeTypes, $type, $type);
    }

    public function create_expired_option_market(string $symbol) {
        // support expired option contracts
        $quote = 'USD';
        $optionParts = explode('-', $symbol);
        $symbolBase = explode('/', $symbol);
        $base = null;
        if (mb_strpos($symbol, '/') > -1) {
            $base = $this->safe_string($symbolBase, 0);
        } else {
            $base = $this->safe_string($optionParts, 0);
        }
        $settle = $base;
        $expiry = $this->safe_string($optionParts, 2);
        $strike = $this->safe_string($optionParts, 3);
        $optionType = $this->safe_string($optionParts, 4);
        $datetime = $this->convert_expire_date($expiry);
        $timestamp = $this->parse8601($datetime);
        return array(
            'id' => $base . '-' . $quote . '-' . $expiry . '-' . $strike . '-' . $optionType,
            'symbol' => $base . '/' . $quote . ':' . $settle . '-' . $expiry . '-' . $strike . '-' . $optionType,
            'base' => $base,
            'quote' => $quote,
            'settle' => $settle,
            'baseId' => $base,
            'quoteId' => $quote,
            'settleId' => $settle,
            'active' => false,
            'type' => 'option',
            'linear' => null,
            'inverse' => null,
            'spot' => false,
            'swap' => false,
            'future' => false,
            'option' => true,
            'margin' => false,
            'contract' => true,
            'contractSize' => $this->parse_number('1'),
            'expiry' => $timestamp,
            'expiryDatetime' => $datetime,
            'optionType' => ($optionType === 'C') ? 'call' : 'put',
            'strike' => $this->parse_number($strike),
            'precision' => array(
                'amount' => null,
                'price' => null,
            ),
            'limits' => array(
                'amount' => array(
                    'min' => null,
                    'max' => null,
                ),
                'price' => array(
                    'min' => null,
                    'max' => null,
                ),
                'cost' => array(
                    'min' => null,
                    'max' => null,
                ),
            ),
            'info' => null,
        );
    }

    public function safe_market(?string $marketId = null, ?array $market = null, ?string $delimiter = null, ?string $marketType = null): array {
        $isOption = ($marketId !== null) && ((mb_strpos($marketId, '-C') > -1) || (mb_strpos($marketId, '-P') > -1));
        if ($isOption && !(is_array($this->markets_by_id) && array_key_exists($marketId, $this->markets_by_id))) {
            // handle expired option contracts
            return $this->create_expired_option_market($marketId);
        }
        return parent::safe_market($marketId, $market, $delimiter, $marketType);
    }

    public function fetch_status($params = array ()) {
        return Async\async(function () use ($params) {
            /**
             * the latest known information on the availability of the exchange API
             *
             * @see https://www.okx.com/docs-v5/en/#status-get-status
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=exchange-status-structure status structure~
             */
            $response = Async\await($this->publicGetSystemStatus ($params));
            //
            // Note, if there is no maintenance around, the 'data' array is empty
            //
            //     {
            //         "code" => "0",
            //         "msg" => "",
            //         "data" => array(
            //             array(
            //                 "begin" => "1621328400000",
            //                 "end" => "1621329000000",
            //                 "href" => "https://www.okx.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"
            //             ),
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $dataLength = count($data);
            $update = array(
                'updated' => null,
                'status' => ($dataLength === 0) ? 'ok' : 'maintenance',
                'eta' => null,
                'url' => null,
                'info' => $response,
            );
            for ($i = 0; $i < count($data); $i++) {
                $event = $data[$i];
                $state = $this->safe_string($event, 'state');
                $update['eta'] = $this->safe_integer($event, 'end');
                $update['url'] = $this->safe_string($event, 'href');
                if ($state === 'ongoing') {
                    $update['status'] = 'maintenance';
                } elseif ($state === 'scheduled') {
                    $update['status'] = 'ok';
                } elseif ($state === 'completed') {
                    $update['status'] = 'ok';
                } elseif ($state === 'canceled') {
                    $update['status'] = 'ok';
                }
            }
            return $update;
        }) ();
    }

    public function fetch_time($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetches the current integer timestamp in milliseconds from the exchange server
             *
             * @see https://www.okx.com/docs-v5/en/#public-$data-rest-api-get-system-time
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {int} the current integer timestamp in milliseconds from the exchange server
             */
            $response = Async\await($this->publicGetPublicTime ($params));
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             array("ts" => "1621247923668")
            //         ),
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $first = $this->safe_dict($data, 0, array());
            return $this->safe_integer($first, 'ts');
        }) ();
    }

    public function fetch_accounts($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetch all the accounts associated with a profile
             *
             * @see https://www.okx.com/docs-v5/en/#trading-$account-rest-api-get-$account-configuration
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=$account-structure $account structures~ indexed by the $account $type
             */
            $response = Async\await($this->privateGetAccountConfig ($params));
            //
            //     {
            //         "code" => "0",
            //         "data" => [
            //             {
            //                 "acctLv" => "2",
            //                 "acctStpMode" => "cancel_maker",
            //                 "autoLoan" => false,
            //                 "ctIsoMode" => "automatic",
            //                 "enableSpotBorrow" => false,
            //                 "greeksType" => "PA",
            //                 "feeType" => "0",
            //                 "ip" => "",
            //                 "type" => "0",
            //                 "kycLv" => "3",
            //                 "label" => "v5 test",
            //                 "level" => "Lv1",
            //                 "levelTmp" => "",
            //                 "liquidationGear" => "-1",
            //                 "mainUid" => "44705892343619584",
            //                 "mgnIsoMode" => "automatic",
            //                 "opAuth" => "1",
            //                 "perm" => "read_only,withdraw,trade",
            //                 "posMode" => "long_short_mode",
            //                 "roleType" => "0",
            //                 "spotBorrowAutoRepay" => false,
            //                 "spotOffsetType" => "",
            //                 "spotRoleType" => "0",
            //                 "spotTraderInsts" => array(),
            //                 "traderInsts" => array(),
            //                 "uid" => "44705892343619584",
            //                 "settleCcy" => "USDT",
            //                 "settleCcyList" => ["USD", "USDC", "USDG"],
            //             }
            //         ],
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $result = array();
            for ($i = 0; $i < count($data); $i++) {
                $account = $data[$i];
                $accountId = $this->safe_string($account, 'uid');
                $type = $this->safe_string($account, 'acctLv');
                $result[] = array(
                    'id' => $accountId,
                    'type' => $type,
                    'currency' => null,
                    'info' => $account,
                    'code' => null,
                );
            }
            return $result;
        }) ();
    }

    public function nonce() {
        return $this->milliseconds() - $this->options['timeDifference'];
    }

    public function fetch_markets($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * retrieves data on all markets for okx
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-public-data-get-instruments
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} an array of objects representing market data
             */
            if ($this->options['adjustForTimeDifference']) {
                Async\await($this->load_time_difference());
            }
            $types = array( 'spot', 'future', 'swap', 'option' );
            $fetchMarketsOption = $this->safe_dict($this->options, 'fetchMarkets');
            if ($fetchMarketsOption !== null) {
                $types = $this->safe_list($fetchMarketsOption, 'types', $types);
            } else {
                $types = $this->safe_list($this->options, 'fetchMarkets', $types); // backward-support
            }
            $promises = array();
            $result = array();
            for ($i = 0; $i < count($types); $i++) {
                $promises[] = $this->fetch_markets_by_type($types[$i], $params);
            }
            $promises = Async\await(Promise\all($promises));
            for ($i = 0; $i < count($promises); $i++) {
                $result = $this->array_concat($result, $promises[$i]);
            }
            return $result;
        }) ();
    }

    public function parse_market(array $market): array {
        //
        //     {
        //         "alias" => "", // this_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",
        //         "contTdSwTime" => "1631262812280",
        //         "lotSz" => "1",
        //         "minSz" => "1",
        //         "optType" => "P",
        //         "quoteCcy" => "",
        //         "settleCcy" => "BTC",
        //         "state" => "live",
        //         "stk" => "194000",
        //         "tickSz" => "0.0005",
        //         "uly" => "BTC-USD"
        //     }
        //
        $id = $this->safe_string($market, 'instId');
        $type = $this->safe_string_lower($market, 'instType');
        if ($type === 'futures') {
            $type = 'future';
        }
        $spot = ($type === 'spot');
        $future = ($type === 'future');
        $swap = ($type === 'swap');
        $option = ($type === 'option');
        $contract = $swap || $future || $option;
        $baseId = $this->safe_string($market, 'baseCcy', ''); // defaulting to '' because some weird preopen markets have empty $baseId
        $quoteId = $this->safe_string($market, 'quoteCcy', '');
        $settleId = $this->safe_string($market, 'settleCcy');
        $settle = $this->safe_currency_code($settleId);
        $underlying = $this->safe_string($market, 'uly');
        if (($underlying !== null) && !$spot) {
            $parts = explode('-', $underlying);
            $baseId = $this->safe_string($parts, 0);
            $quoteId = $this->safe_string($parts, 1);
        }
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $symbol = $base . '/' . $quote;
        $expiry = null;
        $strikePrice = null;
        $optionType = null;
        if ($contract) {
            if ($settle !== null) {
                $symbol = $symbol . ':' . $settle;
            }
            if ($future) {
                $expiry = $this->safe_integer($market, 'expTime');
                if ($expiry !== null) {
                    $ymd = $this->yymmdd($expiry);
                    $symbol = $symbol . '-' . $ymd;
                }
            } elseif ($option) {
                $expiry = $this->safe_integer($market, 'expTime');
                $strikePrice = $this->safe_string($market, 'stk');
                $optionType = $this->safe_string($market, 'optType');
                if ($expiry !== null) {
                    $ymd = $this->yymmdd($expiry);
                    $symbol = $symbol . '-' . $ymd . '-' . $strikePrice . '-' . $optionType;
                    $optionType = ($optionType === 'P') ? 'put' : 'call';
                }
            }
        }
        $fees = $this->safe_dict_2($this->fees, $type, 'trading', array());
        $maxLeverage = $this->safe_string($market, 'lever', '1');
        $maxLeverage = Precise::string_max($maxLeverage, '1');
        $maxSpotCost = $this->safe_number($market, 'maxMktSz');
        return $this->extend($fees, array(
            'id' => $id,
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'settle' => $settle,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'settleId' => $settleId,
            'type' => $type,
            'spot' => $spot,
            'margin' => $spot && (Precise::string_gt($maxLeverage, '1')),
            'swap' => $swap,
            'future' => $future,
            'option' => $option,
            'active' => true,
            'contract' => $contract,
            'linear' => $contract ? ($quoteId === $settleId) : null,
            'inverse' => $contract ? ($baseId === $settleId) : null,
            'contractSize' => $contract ? $this->safe_number($market, 'ctVal') : null,
            'expiry' => $expiry,
            'expiryDatetime' => $this->iso8601($expiry),
            'strike' => $this->parse_number($strikePrice),
            'optionType' => $optionType,
            'created' => $this->safe_integer_2($market, 'contTdSwTime', 'listTime'), // contTdSwTime is public trading start time, while listTime considers pre-trading too
            'precision' => array(
                'amount' => $this->safe_number($market, 'lotSz'),
                'price' => $this->safe_number($market, 'tickSz'),
            ),
            'limits' => array(
                'leverage' => array(
                    'min' => $this->parse_number('1'),
                    'max' => $this->parse_number($maxLeverage),
                ),
                'amount' => array(
                    'min' => $this->safe_number($market, 'minSz'),
                    'max' => null,
                ),
                'price' => array(
                    'min' => null,
                    'max' => null,
                ),
                'cost' => array(
                    'min' => null,
                    'max' => $contract ? null : $maxSpotCost,
                ),
            ),
            'info' => $market,
        ));
    }

    public function fetch_markets_by_type($type, $params = array ()) {
        return Async\async(function () use ($type, $params) {
            $request = array(
                'instType' => $this->convert_to_instrument_type($type),
            );
            if ($type === 'option') {
                $optionsUnderlying = $this->safe_list($this->options, 'defaultUnderlying', array( 'BTC-USD', 'ETH-USD' ));
                $promises = array();
                for ($i = 0; $i < count($optionsUnderlying); $i++) {
                    $underlying = $optionsUnderlying[$i];
                    $request['uly'] = $underlying;
                    $promises[] = $this->publicGetPublicInstruments ($this->extend($request, $params));
                }
                $promisesResult = Async\await(Promise\all($promises));
                $markets = array();
                for ($i = 0; $i < count($promisesResult); $i++) {
                    $res = $this->safe_dict($promisesResult, $i, array());
                    $options = $this->safe_list($res, 'data', array());
                    $markets = $this->array_concat($markets, $options);
                }
                return $this->parse_markets($markets);
            }
            $response = Async\await($this->publicGetPublicInstruments ($this->extend($request, $params)));
            //
            // spot, future, swap, option
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "alias" => "", // this_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" => ""
            //     }
            //
            $dataResponse = $this->safe_list($response, 'data', array());
            return $this->parse_markets($dataResponse);
        }) ();
    }

    public function fetch_currencies($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetches all available currencies on an exchange
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-funding-get-currencies
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an associative dictionary of currencies
             */
            // this 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
            $isSandboxMode = $this->safe_bool($this->options, 'sandboxMode', false);
            if (!$this->check_required_credentials(false) || $isSandboxMode) {
                return array();
            }
            //
            // has['fetchCurrencies'] is currently set to true, but an unauthorized request returns
            //
            //     array("msg":"Request header “OK_ACCESS_KEY“ can't be empty.","code":"50103")
            //
            $response = Async\await($this->privateGetAssetCurrencies ($params));
            //
            //    {
            //        "code" => "0",
            //        "data" => array(
            //            array(
            //                "canDep" => true,
            //                "canInternal" => false,
            //                "canWd" => true,
            //                "ccy" => "USDT",
            //                "chain" => "USDT-TRC20",
            //                "logoLink" => "https://static.coinall.ltd/cdn/assets/imgs/221/5F74EB20302D7761.png",
            //                "mainNet" => false,
            //                "maxFee" => "1.6",
            //                "maxWd" => "8852150",
            //                "minFee" => "0.8",
            //                "minWd" => "2",
            //                "name" => "Tether",
            //                "usedWdQuota" => "0",
            //                "wdQuota" => "500",
            //                "wdTickSz" => "3"
            //            ),
            //            array(
            //                "canDep" => true,
            //                "canInternal" => false,
            //                "canWd" => true,
            //                "ccy" => "USDT",
            //                "chain" => "USDT-ERC20",
            //                "logoLink" => "https://static.coinall.ltd/cdn/assets/imgs/221/5F74EB20302D7761.png",
            //                "mainNet" => false,
            //                "maxFee" => "16",
            //                "maxWd" => "8852150",
            //                "minFee" => "8",
            //                "minWd" => "2",
            //                "name" => "Tether",
            //                "usedWdQuota" => "0",
            //                "wdQuota" => "500",
            //                "wdTickSz" => "3"
            //            ),
            //            ...
            //        ),
            //        "msg" => ""
            //    }
            //
            $data = $this->safe_list($response, 'data', array());
            $result = array();
            $dataByCurrencyId = $this->group_by($data, 'ccy');
            $currencyIds = is_array($dataByCurrencyId) ? array_keys($dataByCurrencyId) : array();
            for ($i = 0; $i < count($currencyIds); $i++) {
                $currencyId = $currencyIds[$i];
                $currency = $this->safe_currency($currencyId);
                $code = $currency['code'];
                $chains = $dataByCurrencyId[$currencyId];
                $networks = array();
                $type = 'crypto';
                $chainsLength = count($chains);
                for ($j = 0; $j < $chainsLength; $j++) {
                    $chain = $chains[$j];
                    // allow empty string for rare fiat-currencies, e.g. TRY
                    $networkId = $this->safe_string($chain, 'chain', ''); // USDT-BEP20, USDT-Avalance-C, etc
                    if ($networkId === '') {
                        // only happens for fiat 'TRY' $currency
                        $type = 'fiat';
                    }
                    $idParts = explode('-', $networkId);
                    $parts = $this->array_slice($idParts, 1);
                    $chainPart = implode('-', $parts);
                    $networkCode = $this->network_id_to_code($chainPart, $currency['code']);
                    $networks[$networkCode] = array(
                        'id' => $networkId,
                        'network' => $networkCode,
                        'active' => null,
                        'deposit' => $this->safe_bool($chain, 'canDep'),
                        'withdraw' => $this->safe_bool($chain, 'canWd'),
                        'fee' => $this->safe_number($chain, 'fee'),
                        'precision' => $this->parse_number($this->parse_precision($this->safe_string($chain, 'wdTickSz'))),
                        'limits' => array(
                            'withdraw' => array(
                                'min' => $this->safe_number($chain, 'minWd'),
                                'max' => $this->safe_number($chain, 'maxWd'),
                            ),
                        ),
                        'info' => $chain,
                    );
                }
                $firstChain = $this->safe_dict($chains, 0, array());
                $result[$code] = $this->safe_currency_structure(array(
                    'info' => $chains,
                    'code' => $code,
                    'id' => $currencyId,
                    'name' => $this->safe_string($firstChain, 'name'),
                    'active' => null,
                    'deposit' => null,
                    'withdraw' => null,
                    'fee' => null,
                    'precision' => null,
                    'limits' => array(
                        'amount' => array(
                            'min' => null,
                            'max' => null,
                        ),
                    ),
                    'type' => $type,
                    'networks' => $networks,
                ));
            }
            return $result;
        }) ();
    }

    public function fetch_order_book(string $symbol, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $limit, $params) {
            /**
             * fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other $data
             *
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-$market-$data-get-order-book
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the order book for
             * @param {int} [$limit] the maximum amount of order book entries to return
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->method] 'publicGetMarketBooksFull' or 'publicGetMarketBooks' default is 'publicGetMarketBooks'
             * @return {array} A dictionary of ~@link https://docs.ccxt.com/#/?id=order-book-structure order book structures~ indexed by $market symbols
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'instId' => $market['id'],
            );
            $method = null;
            list($method, $params) = $this->handle_option_and_params($params, 'fetchOrderBook', 'method', 'publicGetMarketBooks');
            if ($method === 'publicGetMarketBooksFull' && $limit === null) {
                $limit = 5000;
            }
            $limit = ($limit === null) ? 100 : $limit;
            if ($limit !== null) {
                $request['sz'] = $limit; // max 400
            }
            $response = null;
            if (($method === 'publicGetMarketBooksFull') || ($limit > 400)) {
                $response = Async\await($this->publicGetMarketBooksFull ($this->extend($request, $params)));
            } else {
                $response = Async\await($this->publicGetMarketBooks ($this->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 = $this->safe_list($response, 'data', array());
            $first = $this->safe_dict($data, 0, array());
            $timestamp = $this->safe_integer($first, 'ts');
            return $this->parse_order_book($first, $symbol, $timestamp);
        }) ();
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        //
        //      {
        //          "instType":"SWAP",
        //          "instId":"BTC-USDT-SWAP",
        //          "markPx":"200",
        //          "ts":"1597026383085"
        //      }
        //
        //     {
        //         "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"
        //     }
        //     array(
        //          instId => 'LTC-USDT',
        //          idxPx => '65.74',
        //          open24h => '65.37',
        //          high24h => '66.15',
        //          low24h => '64.97',
        //          sodUtc0 => '65.68',
        //          sodUtc8 => '65.54',
        //          ts => '1728467346900'
        //     ),
        //
        $timestamp = $this->safe_integer($ticker, 'ts');
        $marketId = $this->safe_string($ticker, 'instId');
        $market = $this->safe_market($marketId, $market, '-');
        $symbol = $market['symbol'];
        $last = $this->safe_string($ticker, 'last');
        $open = $this->safe_string($ticker, 'open24h');
        $spot = $this->safe_bool($market, 'spot', false);
        $quoteVolume = $spot ? $this->safe_string($ticker, 'volCcy24h') : null;
        $baseVolume = $this->safe_string($ticker, 'vol24h');
        $high = $this->safe_string($ticker, 'high24h');
        $low = $this->safe_string($ticker, 'low24h');
        return $this->safe_ticker(array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $high,
            'low' => $low,
            'bid' => $this->safe_string($ticker, 'bidPx'),
            'bidVolume' => $this->safe_string($ticker, 'bidSz'),
            'ask' => $this->safe_string($ticker, 'askPx'),
            'askVolume' => $this->safe_string($ticker, 'askSz'),
            'vwap' => null,
            'open' => $open,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => null,
            'percentage' => null,
            'average' => null,
            'baseVolume' => $baseVolume,
            'quoteVolume' => $quoteVolume,
            'markPrice' => $this->safe_string($ticker, 'markPx'),
            'indexPrice' => $this->safe_string($ticker, 'idxPx'),
            'info' => $ticker,
        ), $market);
    }

    public function fetch_ticker(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific $market
             *
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-$market-$data-get-ticker
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the ticker for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'instId' => $market['id'],
            );
            $response = Async\await($this->publicGetMarketTicker ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "msg" => "",
            //         "data" => array(
            //             {
            //                 "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 = $this->safe_list($response, 'data', array());
            $first = $this->safe_dict($data, 0, array());
            return $this->parse_ticker($first, $market);
        }) ();
    }

    public function fetch_tickers(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetches price $tickers for multiple markets, statistical information calculated over the past 24 hours for each $market
             *
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-$market-data-get-$tickers
             *
             * @param {string[]} [$symbols] unified $symbols of the markets to fetch the ticker for, all $market $tickers are returned if not assigned
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structures~
             */
            Async\await($this->load_markets());
            $symbols = $this->market_symbols($symbols);
            $market = $this->get_market_from_symbols($symbols);
            $marketType = null;
            list($marketType, $params) = $this->handle_market_type_and_params('fetchTickers', $market, $params);
            $request = array(
                'instType' => $this->convert_to_instrument_type($marketType),
            );
            if ($marketType === 'option') {
                $defaultUnderlying = $this->safe_string($this->options, 'defaultUnderlying', 'BTC-USD');
                $currencyId = $this->safe_string_2($params, 'uly', 'marketId', $defaultUnderlying);
                if ($currencyId === null) {
                    throw new ArgumentsRequired($this->id . ' fetchTickers() requires an underlying uly or marketId parameter for options markets');
                } else {
                    $request['uly'] = $currencyId;
                }
            }
            $response = Async\await($this->publicGetMarketTickers ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "msg" => "",
            //         "data" => array(
            //             array(
            //                 "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 = $this->safe_list($response, 'data', array());
            return $this->parse_tickers($tickers, $symbols);
        }) ();
    }

    public function fetch_mark_price(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetches mark price for the $market
             *
             * @see https://www.okx.com/docs-v5/en/#public-$data-rest-api-get-mark-price
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the ticker for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structures~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'instId' => $market['id'],
            );
            $response = Async\await($this->publicGetPublicMarkPrice ($this->extend($request, $params)));
            //
            // {
            //     "code" => "0",
            //     "data" => array(
            //         {
            //             "instId" => "ETH-USDT",
            //             "instType" => "MARGIN",
            //             "markPx" => "2403.98",
            //             "ts" => "1728578500703"
            //         }
            //     ),
            //     "msg" => ""
            // }
            //
            $data = $this->safe_list($response, 'data');
            return $this->parse_ticker($this->safe_dict($data, 0), $market);
        }) ();
    }

    public function fetch_mark_prices(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetches price $tickers for multiple markets, statistical information calculated over the past 24 hours for each $market
             *
             * @see https://www.okx.com/docs-v5/en/#public-data-rest-api-get-mark-price
             *
             * @param {string[]} [$symbols] unified $symbols of the markets to fetch the ticker for, all $market $tickers are returned if not assigned
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structures~
             */
            Async\await($this->load_markets());
            $symbols = $this->market_symbols($symbols);
            $market = $this->get_market_from_symbols($symbols);
            $marketType = null;
            list($marketType, $params) = $this->handle_market_type_and_params('fetchTickers', $market, $params, 'swap');
            $request = array(
                'instType' => $this->convert_to_instrument_type($marketType),
            );
            if ($marketType === 'option') {
                $defaultUnderlying = $this->safe_string($this->options, 'defaultUnderlying', 'BTC-USD');
                $currencyId = $this->safe_string_2($params, 'uly', 'marketId', $defaultUnderlying);
                if ($currencyId === null) {
                    throw new ArgumentsRequired($this->id . ' fetchMarkPrices() requires an underlying uly or marketId parameter for options markets');
                } else {
                    $request['uly'] = $currencyId;
                }
            }
            $response = Async\await($this->publicGetPublicMarkPrice ($this->extend($request, $params)));
            $tickers = $this->safe_list($response, 'data', array());
            return $this->parse_tickers($tickers, $symbols);
        }) ();
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // public fetchTrades
        //
        //     {
        //         "instId" => "ETH-BTC",
        //         "side" => "sell",
        //         "sz" => "0.119501",
        //         "px" => "0.07065",
        //         "tradeId" => "15826757",
        //         "ts" => "1621446178316"
        //     }
        //
        // option => fetchTrades
        //
        //     {
        //         "fillVol" => "0.46387625976562497",
        //         "fwdPx" => "26299.754935451125",
        //         "indexPx" => "26309.7",
        //         "instFamily" => "BTC-USD",
        //         "instId" => "BTC-USD-230526-26000-C",
        //         "markPx" => "0.042386283557554236",
        //         "optType" => "C",
        //         "px" => "0.0415",
        //         "side" => "sell",
        //         "sz" => "90",
        //         "tradeId" => "112",
        //         "ts" => "1683907480154"
        //     }
        //
        // 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 = $this->safe_string($trade, 'tradeId');
        $marketId = $this->safe_string($trade, 'instId');
        $market = $this->safe_market($marketId, $market, '-');
        $symbol = $market['symbol'];
        $timestamp = $this->safe_integer($trade, 'ts');
        $price = $this->safe_string_2($trade, 'fillPx', 'px');
        $amount = $this->safe_string_2($trade, 'fillSz', 'sz');
        $side = $this->safe_string($trade, 'side');
        $orderId = $this->safe_string($trade, 'ordId');
        $feeCostString = $this->safe_string($trade, 'fee');
        $fee = null;
        if ($feeCostString !== null) {
            $feeCostSigned = Precise::string_neg($feeCostString);
            $feeCurrencyId = $this->safe_string($trade, 'feeCcy');
            $feeCurrencyCode = $this->safe_currency_code($feeCurrencyId);
            $fee = array(
                'cost' => $feeCostSigned,
                'currency' => $feeCurrencyCode,
            );
        }
        $takerOrMaker = $this->safe_string($trade, 'execType');
        if ($takerOrMaker === 'T') {
            $takerOrMaker = 'taker';
        } elseif ($takerOrMaker === 'M') {
            $takerOrMaker = 'maker';
        }
        return $this->safe_trade(array(
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'id' => $id,
            'order' => $orderId,
            'type' => null,
            'takerOrMaker' => $takerOrMaker,
            'side' => $side,
            'price' => $price,
            'amount' => $amount,
            'cost' => null,
            'fee' => $fee,
        ), $market);
    }

    public function fetch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * get the list of most recent trades for a particular $symbol
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-$market-$data-get-trades
             * @see https://www.okx.com/docs-v5/en/#rest-api-public-$data-get-option-trades
             *
             * @param {string} $symbol unified $symbol of the $market to fetch trades for
             * @param {int} [$since] timestamp in ms of the earliest trade to fetch
             * @param {int} [$limit] the maximum amount of trades to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->method] 'publicGetMarketTrades' or 'publicGetMarketHistoryTrades' default is 'publicGetMarketTrades'
             * @param {boolean} [$params->paginate] *only applies to publicGetMarketHistoryTrades* default false, when true will automatically $paginate by calling this endpoint multiple times
             * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-trades trade structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchTrades', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_cursor('fetchTrades', $symbol, $since, $limit, $params, 'tradeId', 'after', null, 100));
            }
            $market = $this->market($symbol);
            $request = array(
                'instId' => $market['id'],
            );
            $response = null;
            if ($market['option']) {
                $response = Async\await($this->publicGetPublicOptionTrades ($this->extend($request, $params)));
            } else {
                if ($limit !== null) {
                    $request['limit'] = $limit; // default 100
                }
                $method = null;
                list($method, $params) = $this->handle_option_and_params($params, 'fetchTrades', 'method', 'publicGetMarketTrades');
                if ($method === 'publicGetMarketTrades') {
                    $response = Async\await($this->publicGetMarketTrades ($this->extend($request, $params)));
                } elseif ($method === 'publicGetMarketHistoryTrades') {
                    $response = Async\await($this->publicGetMarketHistoryTrades ($this->extend($request, $params)));
                }
            }
            //
            //     {
            //         "code" => "0",
            //         "msg" => "",
            //         "data" => array(
            //             array("instId":"ETH-BTC","side":"sell","sz":"0.119501","px":"0.07065","tradeId":"15826757","ts":"1621446178316"),
            //             array("instId":"ETH-BTC","side":"sell","sz":"0.03","px":"0.07068","tradeId":"15826756","ts":"1621446178066"),
            //             array("instId":"ETH-BTC","side":"buy","sz":"0.507","px":"0.07069","tradeId":"15826755","ts":"1621446175085"),
            //         )
            //     }
            //
            // option
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             array(
            //                 "fillVol" => "0.46387625976562497",
            //                 "fwdPx" => "26299.754935451125",
            //                 "indexPx" => "26309.7",
            //                 "instFamily" => "BTC-USD",
            //                 "instId" => "BTC-USD-230526-26000-C",
            //                 "markPx" => "0.042386283557554236",
            //                 "optType" => "C",
            //                 "px" => "0.0415",
            //                 "side" => "sell",
            //                 "sz" => "90",
            //                 "tradeId" => "112",
            //                 "ts" => "1683907480154"
            //             ),
            //         ),
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_trades($data, $market, $since, $limit);
        }) ();
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        //
        //     array(
        //         "1678928760000", // timestamp
        //         "24341.4", // open
        //         "24344", // high
        //         "24313.2", // low
        //         "24323", // close
        //         "628", // contract volume
        //         "2.5819", // base volume
        //         "62800", // quote volume
        //         "0" // candlestick state
        //     )
        //
        $res = $this->handle_market_type_and_params('fetchOHLCV', $market, null);
        $type = $res[0];
        $volumeIndex = ($type === 'spot') ? 5 : 6;
        return array(
            $this->safe_integer($ohlcv, 0),
            $this->safe_number($ohlcv, 1),
            $this->safe_number($ohlcv, 2),
            $this->safe_number($ohlcv, 3),
            $this->safe_number($ohlcv, 4),
            $this->safe_number($ohlcv, $volumeIndex),
        );
    }

    public function fetch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $timeframe, $since, $limit, $params) {
            /**
             * fetches historical candlestick $data containing the open, high, low, and close $price, and the volume of a $market
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-$market-$data-get-candlesticks
             * @see https://www.okx.com/docs-v5/en/#rest-api-$market-$data-get-candlesticks-history
             * @see https://www.okx.com/docs-v5/en/#rest-api-$market-$data-get-mark-$price-candlesticks
             * @see https://www.okx.com/docs-v5/en/#rest-api-$market-$data-get-mark-$price-candlesticks-history
             * @see https://www.okx.com/docs-v5/en/#rest-api-$market-$data-get-index-candlesticks
             * @see https://www.okx.com/docs-v5/en/#rest-api-$market-$data-get-index-candlesticks-history
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-$market-$data-get-candlesticks-history
             *
             * @param {string} $symbol unified $symbol of the $market to fetch OHLCV $data for
             * @param {string} $timeframe the length of time each candle represents
             * @param {int} [$since] timestamp in ms of the earliest candle to fetch
             * @param {int} [$limit] the maximum amount of candles to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->price] "mark" or "index" for mark $price and index $price candles
             * @param {int} [$params->until] timestamp in ms of the latest candle to fetch
             * @param {string} [$params->type] "Candles" or "HistoryCandles", default is "Candles" for recent candles, "HistoryCandles" for older candles
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {int[][]} A list of candles ordered, open, high, low, close, volume
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchOHLCV', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_deterministic('fetchOHLCV', $symbol, $since, $limit, $timeframe, $params, 200));
            }
            $price = $this->safe_string($params, 'price');
            $params = $this->omit($params, 'price');
            $options = $this->safe_dict($this->options, 'fetchOHLCV', array());
            $timezone = $this->safe_string($options, 'timezone', 'UTC');
            $limitIsUndefined = ($limit === null);
            if ($limit === null) {
                $limit = 100; // default 100, max 100
            } else {
                $limit = min ($limit, 300); // max 100
            }
            $duration = $this->parse_timeframe($timeframe);
            $bar = $this->safe_string($this->timeframes, $timeframe, $timeframe);
            if (($timezone === 'UTC') && ($duration >= 21600)) { // if utc and $timeframe >= 6h
                $bar .= strtolower($timezone);
            }
            $request = array(
                'instId' => $market['id'],
                'bar' => $bar,
                'limit' => $limit,
            );
            $defaultType = 'Candles';
            if ($since !== null) {
                $now = $this->milliseconds();
                $durationInMilliseconds = $duration * 1000;
                // switch to history candles if $since is past the cutoff for current candles
                $historyBorder = $now - ((1440 - 1) * $durationInMilliseconds);
                if ($since < $historyBorder) {
                    $defaultType = 'HistoryCandles';
                    $maxLimit = ($price !== null) ? 100 : 300;
                    $limit = min ($limit, $maxLimit); // max 300 for historical endpoint
                }
                $startTime = max ($since - 1, 0);
                $request['before'] = $startTime;
                $request['after'] = $this->sum($since, $durationInMilliseconds * $limit);
            }
            $until = $this->safe_integer($params, 'until');
            if ($until !== null) {
                $request['after'] = $until;
                $params = $this->omit($params, 'until');
            }
            $defaultType = $this->safe_string($options, 'type', $defaultType); // Candles or HistoryCandles
            $type = $this->safe_string($params, 'type', $defaultType);
            $params = $this->omit($params, 'type');
            $isHistoryCandles = ($type === 'HistoryCandles');
            $response = null;
            if ($price === 'mark') {
                if ($isHistoryCandles) {
                    $response = Async\await($this->publicGetMarketHistoryMarkPriceCandles ($this->extend($request, $params)));
                } else {
                    $response = Async\await($this->publicGetMarketMarkPriceCandles ($this->extend($request, $params)));
                }
            } elseif ($price === 'index') {
                $request['instId'] = $market['info']['instFamily']; // okx index candles require instFamily instead of instId
                if ($isHistoryCandles) {
                    $response = Async\await($this->publicGetMarketHistoryIndexCandles ($this->extend($request, $params)));
                } else {
                    $response = Async\await($this->publicGetMarketIndexCandles ($this->extend($request, $params)));
                }
            } else {
                if ($isHistoryCandles) {
                    if ($limitIsUndefined && ($limit === 100)) {
                        $limit = 300;
                        $request['limit'] = 300; // reassign to 300, but this whole logic needs to be simplified...
                    }
                    $response = Async\await($this->publicGetMarketHistoryCandles ($this->extend($request, $params)));
                } else {
                    $response = Async\await($this->publicGetMarketCandles ($this->extend($request, $params)));
                }
            }
            //
            //     {
            //         "code" => "0",
            //         "msg" => "",
            //         "data" => [
            //             ["1678928760000","24341.4","24344","24313.2","24323","628","2.5819","62800","0"],
            //             ["1678928700000","24324.1","24347.6","24321.7","24341.4","2565","10.5401","256500","1"],
            //             ["1678928640000","24300.2","24324.1","24288","24324.1","3304","13.5937","330400","1"],
            //         ]
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_ohlcvs($data, $market, $timeframe, $since, $limit);
        }) ();
    }

    public function fetch_funding_rate_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetches historical funding $rate prices
             *
             * @see https://www.okx.com/docs-v5/en/#public-$data-rest-api-get-funding-$rate-history
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the funding $rate history for
             * @param {int} [$since] $timestamp in ms of the earliest funding $rate to fetch
             * @param {int} [$limit] the maximum amount of ~@link https://docs.ccxt.com/#/?id=funding-$rate-history-structure funding $rate structures~ to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=funding-$rate-history-structure funding $rate structures~
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' fetchFundingRateHistory() requires a $symbol argument');
            }
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchFundingRateHistory', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_deterministic('fetchFundingRateHistory', $symbol, $since, $limit, '8h', $params, 100));
            }
            $market = $this->market($symbol);
            $request = array(
                'instId' => $market['id'],
            );
            if ($since !== null) {
                $request['before'] = max ($since - 1, 0);
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $response = Async\await($this->publicGetPublicFundingRateHistory ($this->extend($request, $params)));
            //
            //     {
            //         "code":"0",
            //         "msg":"",
            //         "data":array(
            //             array(
            //                 "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 = array();
            $data = $this->safe_list($response, 'data', array());
            for ($i = 0; $i < count($data); $i++) {
                $rate = $data[$i];
                $timestamp = $this->safe_integer($rate, 'fundingTime');
                $rates[] = array(
                    'info' => $rate,
                    'symbol' => $this->safe_symbol($this->safe_string($rate, 'instId')),
                    'fundingRate' => $this->safe_number($rate, 'realizedRate'),
                    'timestamp' => $timestamp,
                    'datetime' => $this->iso8601($timestamp),
                );
            }
            $sorted = $this->sort_by($rates, 'timestamp');
            return $this->filter_by_symbol_since_limit($sorted, $market['symbol'], $since, $limit);
        }) ();
    }

    public function parse_balance_by_type($type, $response) {
        if ($type === 'funding') {
            return $this->parse_funding_balance($response);
        } else {
            return $this->parse_trading_balance($response);
        }
    }

    public function parse_trading_balance($response) {
        $result = array( 'info' => $response );
        $data = $this->safe_list($response, 'data', array());
        $first = $this->safe_dict($data, 0, array());
        $timestamp = $this->safe_integer($first, 'uTime');
        $details = $this->safe_list($first, 'details', array());
        for ($i = 0; $i < count($details); $i++) {
            $balance = $details[$i];
            $currencyId = $this->safe_string($balance, 'ccy');
            $code = $this->safe_currency_code($currencyId);
            $account = $this->account();
            // it may be incorrect to use total, free and used for swap accounts
            $eq = $this->safe_string($balance, 'eq');
            $availEq = $this->safe_string($balance, 'availEq');
            $account['total'] = $eq;
            if ($availEq === null) {
                $account['free'] = $this->safe_string($balance, 'availBal');
                $account['used'] = $this->safe_string($balance, 'frozenBal');
            } else {
                $account['free'] = $availEq;
            }
            $result[$code] = $account;
        }
        $result['timestamp'] = $timestamp;
        $result['datetime'] = $this->iso8601($timestamp);
        return $this->safe_balance($result);
    }

    public function parse_funding_balance($response) {
        $result = array( 'info' => $response );
        $data = $this->safe_list($response, 'data', array());
        for ($i = 0; $i < count($data); $i++) {
            $balance = $data[$i];
            $currencyId = $this->safe_string($balance, 'ccy');
            $code = $this->safe_currency_code($currencyId);
            $account = $this->account();
            // it may be incorrect to use total, free and used for swap accounts
            $account['total'] = $this->safe_string($balance, 'bal');
            $account['free'] = $this->safe_string($balance, 'availBal');
            $account['used'] = $this->safe_string($balance, 'frozenBal');
            $result[$code] = $account;
        }
        return $this->safe_balance($result);
    }

    public function parse_trading_fee(array $fee, ?array $market = null): array {
        // https://www.okx.com/docs-v5/en/#rest-api-account-get-$fee-rates
        //
        //     {
        //         "category" => "1",
        //         "delivery" => "",
        //         "exercise" => "",
        //         "instType" => "SPOT",
        //         "level" => "Lv1",
        //         "maker" => "-0.0008",
        //         "taker" => "-0.001",
        //         "ts" => "1639043138472"
        //     }
        //
        return array(
            'info' => $fee,
            'symbol' => $this->safe_symbol(null, $market),
            // OKX returns the fees values opposed to other exchanges, so the sign needs to be flipped
            'maker' => $this->parse_number(Precise::string_neg($this->safe_string_2($fee, 'maker', 'makerU'))),
            'taker' => $this->parse_number(Precise::string_neg($this->safe_string_2($fee, 'taker', 'takerU'))),
            'percentage' => null,
            'tierBased' => null,
        );
    }

    public function fetch_trading_fee(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetch the trading fees for a $market
             *
             * @see https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-fee-rates
             *
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=fee-structure fee structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'instType' => $this->convert_to_instrument_type($market['type']), // 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'];
            } elseif ($market['swap'] || $market['future'] || $market['option']) {
                $request['uly'] = $market['baseId'] . '-' . $market['quoteId'];
            } else {
                throw new NotSupported($this->id . ' fetchTradingFee() supports spot, swap, future or option markets only');
            }
            $response = Async\await($this->privateGetAccountTradeFee ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "category" => "1",
            //                 "delivery" => "",
            //                 "exercise" => "",
            //                 "instType" => "SPOT",
            //                 "level" => "Lv1",
            //                 "maker" => "-0.0008",
            //                 "taker" => "-0.001",
            //                 "ts" => "1639043138472"
            //             }
            //         ),
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $first = $this->safe_dict($data, 0, array());
            return $this->parse_trading_fee($first, $market);
        }) ();
    }

    public function fetch_balance($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * $query for balance and get the amount of funds available for trading or funds locked in orders
             *
             * @see https://www.okx.com/docs-v5/en/#funding-account-rest-api-get-balance
             * @see https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-balance
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->type] wallet type, ['funding' or 'trading'] default is 'trading'
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
             */
            Async\await($this->load_markets());
            list($marketType, $query) = $this->handle_market_type_and_params('fetchBalance', null, $params);
            $request = array(
                // 'ccy' => 'BTC,ETH', // comma-separated list of currency ids
            );
            $response = null;
            if ($marketType === 'funding') {
                $response = Async\await($this->privateGetAssetBalances ($this->extend($request, $query)));
            } else {
                $response = Async\await($this->privateGetAccountBalance ($this->extend($request, $query)));
            }
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "adjEq" => "",
            //                 "details" => array(
            //                     {
            //                         "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" => array(
            //             {
            //                 "adjEq" => "",
            //                 "details" => array(
            //                     {
            //                         "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" => array(
            //             {
            //                 "availBal" => "0.00005426",
            //                 "bal" => 0.0000542600000000,
            //                 "ccy" => "BTC",
            //                 "frozenBal" => "0"
            //             }
            //         ),
            //         "msg" => ""
            //     }
            //
            return $this->parse_balance_by_type($marketType, $response);
        }) ();
    }

    public function create_market_buy_order_with_cost(string $symbol, float $cost, $params = array ()) {
        return Async\async(function () use ($symbol, $cost, $params) {
            /**
             * create a $market buy order by providing the $symbol and $cost
             *
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-trade-post-place-order
             *
             * @param {string} $symbol unified $symbol of the $market to create an order in
             * @param {float} $cost how much you want to trade in units of the quote currency
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            if (!$market['spot']) {
                throw new NotSupported($this->id . ' createMarketBuyOrderWithCost() supports spot markets only');
            }
            $req = array(
                'createMarketBuyOrderRequiresPrice' => false,
                'tgtCcy' => 'quote_ccy',
            );
            return Async\await($this->create_order($symbol, 'market', 'buy', $cost, null, $this->extend($req, $params)));
        }) ();
    }

    public function create_market_sell_order_with_cost(string $symbol, float $cost, $params = array ()) {
        return Async\async(function () use ($symbol, $cost, $params) {
            /**
             * create a $market buy order by providing the $symbol and $cost
             *
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-trade-post-place-order
             *
             * @param {string} $symbol unified $symbol of the $market to create an order in
             * @param {float} $cost how much you want to trade in units of the quote currency
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            if (!$market['spot']) {
                throw new NotSupported($this->id . ' createMarketSellOrderWithCost() supports spot markets only');
            }
            $req = array(
                'createMarketBuyOrderRequiresPrice' => false,
                'tgtCcy' => 'quote_ccy',
            );
            return Async\await($this->create_order($symbol, 'market', 'sell', $cost, null, $this->extend($req, $params)));
        }) ();
    }

    public function create_order_request(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        $market = $this->market($symbol);
        $request = array(
            'instId' => $market['id'],
            // 'ccy' => $currency['id'], // only applicable to cross MARGIN orders in single-$currency $margin
            // 'clOrdId' => $clientOrderId, // up to 32 characters, must be unique
            // 'tag' => tag, // up to 8 characters
            'side' => $side,
            // 'posSide' => 'long', // long, short, // required in the long/short mode, and can only be long or short (only for future or swap)
            'ordType' => $type,
            // 'ordType' => $type, // privatePostTradeOrder => $market, limit, post_only, $fok, $ioc, optimal_limit_ioc
            // 'ordType' => $type, // privatePostTradeOrderAlgo => $conditional, oco, $trigger, move_order_stop, iceberg, twap
            'sz' => $this->amount_to_precision($symbol, $amount),
            // 'px' => $this->price_to_precision($symbol, $price), // limit orders only
            // 'reduceOnly' => false,
            //
            // 'triggerPx' => 10, // stopPrice ($trigger orders)
            // 'orderPx' => 10, // Order $price if -1, the order will be executed at the $market $price-> ($trigger orders)
            // 'triggerPxType' => 'last', // Conditional default is last, mark or index ($trigger orders)
            //
            // 'tpTriggerPx' => 10, // $takeProfitPrice ($conditional orders)
            // 'tpTriggerPxType' => 'last', // Conditional default is last, mark or index ($conditional orders)
            // 'tpOrdPx' => 10, // Order $price for Take-Profit orders, if -1 will be executed at $market $price ($conditional orders)
            //
            // 'slTriggerPx' => 10, // $stopLossPrice ($conditional orders)
            // 'slTriggerPxType' => 'last', // Conditional default is last, mark or index ($conditional orders)
            // 'slOrdPx' => 10, // Order $price for Stop-Loss orders, if -1 will be executed at $market $price ($conditional orders)
        );
        $spot = $market['spot'];
        $contract = $market['contract'];
        $triggerPrice = $this->safe_value_n($params, array( 'triggerPrice', 'stopPrice', 'triggerPx' ));
        $timeInForce = $this->safe_string($params, 'timeInForce', 'GTC');
        $takeProfitPrice = $this->safe_value_2($params, 'takeProfitPrice', 'tpTriggerPx');
        $tpOrdPx = $this->safe_value($params, 'tpOrdPx', $price);
        $tpTriggerPxType = $this->safe_string($params, 'tpTriggerPxType', 'last');
        $stopLossPrice = $this->safe_value_2($params, 'stopLossPrice', 'slTriggerPx');
        $slOrdPx = $this->safe_value($params, 'slOrdPx', $price);
        $slTriggerPxType = $this->safe_string($params, 'slTriggerPxType', 'last');
        $clientOrderId = $this->safe_string_2($params, 'clOrdId', 'clientOrderId');
        $stopLoss = $this->safe_value($params, 'stopLoss');
        $stopLossDefined = ($stopLoss !== null);
        $takeProfit = $this->safe_value($params, 'takeProfit');
        $takeProfitDefined = ($takeProfit !== null);
        $trailingPercent = $this->safe_string_2($params, 'trailingPercent', 'callbackRatio');
        $isTrailingPercentOrder = $trailingPercent !== null;
        $trailingPrice = $this->safe_string_2($params, 'trailingPrice', 'callbackSpread');
        $isTrailingPriceOrder = $trailingPrice !== null;
        $trigger = ($triggerPrice !== null) || ($type === 'trigger');
        $isReduceOnly = $this->safe_value($params, 'reduceOnly', false);
        $defaultMarginMode = $this->safe_string_2($this->options, 'defaultMarginMode', 'marginMode', 'cross');
        $marginMode = $this->safe_string_2($params, 'marginMode', 'tdMode'); // cross or isolated, tdMode not ommited so be extended into the $request
        $margin = false;
        if (($marginMode !== null) && ($marginMode !== 'cash')) {
            $margin = true;
        } else {
            $marginMode = $defaultMarginMode;
            $margin = $this->safe_bool($params, 'margin', false);
        }
        if ($spot) {
            if ($margin) {
                $defaultCurrency = ($side === 'buy') ? $market['quote'] : $market['base'];
                $currency = $this->safe_string($params, 'ccy', $defaultCurrency);
                $request['ccy'] = $this->safe_currency_code($currency);
            }
            $tradeMode = $margin ? $marginMode : 'cash';
            $request['tdMode'] = $tradeMode;
        } elseif ($contract) {
            if ($market['swap'] || $market['future']) {
                $positionSide = null;
                list($positionSide, $params) = $this->handle_option_and_params($params, 'createOrder', 'positionSide');
                if ($positionSide !== null) {
                    $request['posSide'] = $positionSide;
                } else {
                    $hedged = null;
                    list($hedged, $params) = $this->handle_option_and_params($params, 'createOrder', 'hedged');
                    if ($hedged) {
                        $isBuy = ($side === 'buy');
                        $isProtective = ($takeProfitPrice !== null) || ($stopLossPrice !== null) || $isReduceOnly;
                        if ($isProtective) {
                            // in case of protective orders, the posSide should be opposite of position $side
                            // reduceOnly is emulated and not natively supported by the exchange
                            $request['posSide'] = $isBuy ? 'short' : 'long';
                            if ($isReduceOnly) {
                                $params = $this->omit($params, 'reduceOnly');
                            }
                        } else {
                            $request['posSide'] = $isBuy ? 'long' : 'short';
                        }
                    }
                }
            }
            $request['tdMode'] = $marginMode;
        }
        $isMarketOrder = $type === 'market';
        $postOnly = false;
        list($postOnly, $params) = $this->handle_post_only($isMarketOrder, $type === 'post_only', $params);
        $params = $this->omit($params, array( 'currency', 'ccy', 'marginMode', 'timeInForce', 'stopPrice', 'triggerPrice', 'clientOrderId', 'stopLossPrice', 'takeProfitPrice', 'slOrdPx', 'tpOrdPx', 'margin', 'stopLoss', 'takeProfit', 'trailingPercent' ));
        $ioc = ($timeInForce === 'IOC') || ($type === 'ioc');
        $fok = ($timeInForce === 'FOK') || ($type === 'fok');
        $conditional = ($stopLossPrice !== null) || ($takeProfitPrice !== null) || ($type === 'conditional');
        $marketIOC = ($isMarketOrder && $ioc) || ($type === 'optimal_limit_ioc');
        $defaultTgtCcy = $this->safe_string($this->options, 'tgtCcy', 'base_ccy');
        $tgtCcy = $this->safe_string($params, 'tgtCcy', $defaultTgtCcy);
        if ((!$contract) && (!$margin)) {
            $request['tgtCcy'] = $tgtCcy;
        }
        if ($isMarketOrder || $marketIOC) {
            $request['ordType'] = 'market';
            if ($spot && ($side === 'buy')) {
                // $spot $market buy => "sz" can refer either to base $currency units or to quote $currency units
                // see documentation => https://www.okx.com/docs-v5/en/#rest-api-trade-place-order
                if ($tgtCcy === 'quote_ccy') {
                    // quote_ccy => sz refers to units of quote $currency
                    $createMarketBuyOrderRequiresPrice = true;
                    list($createMarketBuyOrderRequiresPrice, $params) = $this->handle_option_and_params($params, 'createOrder', 'createMarketBuyOrderRequiresPrice', true);
                    $notional = $this->safe_number_2($params, 'cost', 'sz');
                    $params = $this->omit($params, array( 'cost', 'sz' ));
                    if ($createMarketBuyOrderRequiresPrice) {
                        if ($price !== null) {
                            if ($notional === null) {
                                $amountString = $this->number_to_string($amount);
                                $priceString = $this->number_to_string($price);
                                $quoteAmount = Precise::string_mul($amountString, $priceString);
                                $notional = $this->parse_number($quoteAmount);
                            }
                        } elseif ($notional === null) {
                            throw new InvalidOrder($this->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 'cost' unified extra parameter or in exchange-specific 'sz' extra parameter (the exchange-specific behaviour)");
                        }
                    } else {
                        $notional = ($notional === null) ? $amount : $notional;
                    }
                    $request['sz'] = $this->cost_to_precision($symbol, $notional);
                }
            }
            if ($marketIOC && $contract) {
                $request['ordType'] = 'optimal_limit_ioc';
            }
        } else {
            if ((!$trigger) && (!$conditional)) {
                $request['px'] = $this->price_to_precision($symbol, $price);
            }
        }
        if ($postOnly) {
            $request['ordType'] = 'post_only';
        } elseif ($ioc && !$marketIOC) {
            $request['ordType'] = 'ioc';
        } elseif ($fok) {
            $request['ordType'] = 'fok';
        }
        if ($isTrailingPercentOrder) {
            $convertedTrailingPercent = Precise::string_div($trailingPercent, '100');
            $request['callbackRatio'] = $convertedTrailingPercent;
            $request['ordType'] = 'move_order_stop';
        } elseif ($isTrailingPriceOrder) {
            $request['callbackSpread'] = $trailingPrice;
            $request['ordType'] = 'move_order_stop';
        } elseif ($stopLossDefined || $takeProfitDefined) {
            $attachAlgoOrd = array();
            if ($stopLossDefined) {
                $stopLossTriggerPrice = $this->safe_value_n($stopLoss, array( 'triggerPrice', 'stopPrice', 'slTriggerPx' ));
                if ($stopLossTriggerPrice === null) {
                    throw new InvalidOrder($this->id . ' createOrder() requires a $trigger $price in $params["stopLoss"]["triggerPrice"], or $params["stopLoss"]["stopPrice"], or $params["stopLoss"]["slTriggerPx"] for a stop loss order');
                }
                $slTriggerPx = $this->price_to_precision($symbol, $stopLossTriggerPrice);
                $slOrder = array();
                $slOrder['slTriggerPx'] = $slTriggerPx;
                $stopLossLimitPrice = $this->safe_value_n($stopLoss, array( 'price', 'stopLossPrice', 'slOrdPx' ));
                $stopLossOrderType = $this->safe_string($stopLoss, 'type');
                if ($stopLossOrderType !== null) {
                    $stopLossLimitOrderType = ($stopLossOrderType === 'limit');
                    $stopLossMarketOrderType = ($stopLossOrderType === 'market');
                    if ((!$stopLossLimitOrderType) && (!$stopLossMarketOrderType)) {
                        throw new InvalidOrder($this->id . ' createOrder() $params["stopLoss"]["type"] must be either "limit" or "market"');
                    } elseif ($stopLossLimitOrderType) {
                        if ($stopLossLimitPrice === null) {
                            throw new InvalidOrder($this->id . ' createOrder() requires a limit $price in $params["stopLoss"]["price"] or $params["stopLoss"]["slOrdPx"] for a stop loss limit order');
                        } else {
                            $slOrder['slOrdPx'] = $this->price_to_precision($symbol, $stopLossLimitPrice);
                        }
                    } elseif ($stopLossOrderType === 'market') {
                        $slOrder['slOrdPx'] = '-1';
                    }
                } elseif ($stopLossLimitPrice !== null) {
                    $slOrder['slOrdPx'] = $this->price_to_precision($symbol, $stopLossLimitPrice); // limit sl order
                } else {
                    $slOrder['slOrdPx'] = '-1'; // $market sl order
                }
                $stopLossTriggerPriceType = $this->safe_string_2($stopLoss, 'triggerPriceType', 'slTriggerPxType', 'last');
                if ($stopLossTriggerPriceType !== null) {
                    if (($stopLossTriggerPriceType !== 'last') && ($stopLossTriggerPriceType !== 'index') && ($stopLossTriggerPriceType !== 'mark')) {
                        throw new InvalidOrder($this->id . ' createOrder() stop loss $trigger $price $type must be one of "last", "index" or "mark"');
                    }
                    $slOrder['slTriggerPxType'] = $stopLossTriggerPriceType;
                }
                $attachAlgoOrd = $this->extend($attachAlgoOrd, $slOrder);
            }
            if ($takeProfitDefined) {
                $takeProfitTriggerPrice = $this->safe_value_n($takeProfit, array( 'triggerPrice', 'stopPrice', 'tpTriggerPx' ));
                if ($takeProfitTriggerPrice === null) {
                    throw new InvalidOrder($this->id . ' createOrder() requires a $trigger $price in $params["takeProfit"]["triggerPrice"], or $params["takeProfit"]["stopPrice"], or $params["takeProfit"]["tpTriggerPx"] for a take profit order');
                }
                $tpOrder = array();
                $tpOrder['tpTriggerPx'] = $this->price_to_precision($symbol, $takeProfitTriggerPrice);
                $takeProfitLimitPrice = $this->safe_value_n($takeProfit, array( 'price', 'takeProfitPrice', 'tpOrdPx' ));
                $takeProfitOrderType = $this->safe_string_2($takeProfit, 'type', 'tpOrdKind');
                if ($takeProfitOrderType !== null) {
                    $takeProfitLimitOrderType = ($takeProfitOrderType === 'limit');
                    $takeProfitMarketOrderType = ($takeProfitOrderType === 'market');
                    if ((!$takeProfitLimitOrderType) && (!$takeProfitMarketOrderType)) {
                        throw new InvalidOrder($this->id . ' createOrder() $params["takeProfit"]["type"] must be either "limit" or "market"');
                    } elseif ($takeProfitLimitOrderType) {
                        if ($takeProfitLimitPrice === null) {
                            throw new InvalidOrder($this->id . ' createOrder() requires a limit $price in $params["takeProfit"]["price"] or $params["takeProfit"]["tpOrdPx"] for a take profit limit order');
                        } else {
                            $tpOrder['tpOrdKind'] = $takeProfitOrderType;
                            $tpOrder['tpOrdPx'] = $this->price_to_precision($symbol, $takeProfitLimitPrice);
                        }
                    } elseif ($takeProfitOrderType === 'market') {
                        $tpOrder['tpOrdPx'] = '-1';
                    }
                } elseif ($takeProfitLimitPrice !== null) {
                    $tpOrder['tpOrdKind'] = 'limit';
                    $tpOrder['tpOrdPx'] = $this->price_to_precision($symbol, $takeProfitLimitPrice); // limit tp order
                } else {
                    $tpOrder['tpOrdPx'] = '-1'; // $market tp order
                }
                $takeProfitTriggerPriceType = $this->safe_string_2($takeProfit, 'triggerPriceType', 'tpTriggerPxType', 'last');
                if ($takeProfitTriggerPriceType !== null) {
                    if (($takeProfitTriggerPriceType !== 'last') && ($takeProfitTriggerPriceType !== 'index') && ($takeProfitTriggerPriceType !== 'mark')) {
                        throw new InvalidOrder($this->id . ' createOrder() take profit $trigger $price $type must be one of "last", "index" or "mark"');
                    }
                    $tpOrder['tpTriggerPxType'] = $takeProfitTriggerPriceType;
                }
                $attachAlgoOrd = $this->extend($attachAlgoOrd, $tpOrder);
            }
            $attachOrdKeys = is_array($attachAlgoOrd) ? array_keys($attachAlgoOrd) : array();
            $attachOrdLen = count($attachOrdKeys);
            if ($attachOrdLen > 0) {
                $request['attachAlgoOrds'] = array( $attachAlgoOrd );
            }
        }
        // algo order details
        if ($trigger) {
            $request['ordType'] = 'trigger';
            $request['triggerPx'] = $this->price_to_precision($symbol, $triggerPrice);
            $request['orderPx'] = $isMarketOrder ? '-1' : $this->price_to_precision($symbol, $price);
        } elseif ($conditional) {
            $request['ordType'] = 'conditional';
            $twoWayCondition = (($takeProfitPrice !== null) && ($stopLossPrice !== null));
            // if TP and SL are sent together
            // 'conditional' only stop-loss order will be applied
            // tpOrdKind is 'condition' which is the default
            if ($twoWayCondition) {
                $request['ordType'] = 'oco';
            }
            if ($side === 'sell') {
                $request = $this->omit($request, 'tgtCcy');
            }
            if ($this->safe_string($request, 'tdMode') === 'cash') {
                // for some reason tdMode = cash throws
                // array("code":"1","data":[array("algoClOrdId":"","algoId":"","clOrdId":"","sCode":"51000","sMsg":"Parameter tdMode error ","tag":"")],"msg":"")
                $request['tdMode'] = $marginMode;
            }
            if ($takeProfitPrice !== null) {
                $request['tpTriggerPx'] = $this->price_to_precision($symbol, $takeProfitPrice);
                $tpOrdPxReq = '-1';
                if ($tpOrdPx !== null) {
                    $tpOrdPxReq = $this->price_to_precision($symbol, $tpOrdPx);
                }
                $request['tpOrdPx'] = $tpOrdPxReq;
                $request['tpTriggerPxType'] = $tpTriggerPxType;
            }
            if ($stopLossPrice !== null) {
                $request['slTriggerPx'] = $this->price_to_precision($symbol, $stopLossPrice);
                $slOrdPxReq = '-1';
                if ($slOrdPx !== null) {
                    $slOrdPxReq = $this->price_to_precision($symbol, $slOrdPx);
                }
                $request['slOrdPx'] = $slOrdPxReq;
                $request['slTriggerPxType'] = $slTriggerPxType;
            }
        }
        if ($clientOrderId === null) {
            $brokerId = $this->safe_string($this->options, 'brokerId');
            if ($brokerId !== null) {
                $request['clOrdId'] = $brokerId . $this->uuid16();
                $request['tag'] = $brokerId;
            }
        } else {
            $request['clOrdId'] = $clientOrderId;
            $params = $this->omit($params, array( 'clOrdId', 'clientOrderId' ));
        }
        return $this->extend($request, $params);
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        return Async\async(function () use ($symbol, $type, $side, $amount, $price, $params) {
            /**
             * create a trade $order
             *
             * @see https://www.okx.com/docs-v5/en/#$order-book-trading-trade-post-place-$order
             * @see https://www.okx.com/docs-v5/en/#$order-book-trading-trade-post-place-multiple-orders
             * @see https://www.okx.com/docs-v5/en/#$order-book-trading-algo-trading-post-place-algo-$order
             *
             * @param {string} $symbol unified $symbol of the $market to create an $order in
             * @param {string} $type 'market' or 'limit'
             * @param {string} $side 'buy' or 'sell'
             * @param {float} $amount how much of currency you want to trade in units of base currency
             * @param {float} [$price] the $price at which the $order is to be fulfilled, in units of the quote currency, ignored in $market orders
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {bool} [$params->reduceOnly] a mark to reduce the position size for margin, swap and future orders
             * @param {bool} [$params->postOnly] true to place a post only $order
             * @param {array} [$params->takeProfit] *takeProfit object in $params* containing the triggerPrice at which the attached take profit $order will be triggered (perpetual swap markets only)
             * @param {float} [$params->takeProfit.triggerPrice] take profit trigger $price
             * @param {float} [$params->takeProfit.price] used for take profit limit orders, not used for take profit $market $price orders
             * @param {string} [$params->takeProfit.type] 'market' or 'limit' used to specify the take profit $price $type
             * @param {array} [$params->stopLoss] *stopLoss object in $params* containing the triggerPrice at which the attached stop loss $order will be triggered (perpetual swap markets only)
             * @param {float} [$params->stopLoss.triggerPrice] stop loss trigger $price
             * @param {float} [$params->stopLoss.price] used for stop loss limit orders, not used for stop loss $market $price orders
             * @param {string} [$params->stopLoss.type] 'market' or 'limit' used to specify the stop loss $price $type
             * @param {string} [$params->positionSide] if position mode is one-way => set to 'net', if position mode is hedge-mode => set to 'long' or 'short'
             * @param {string} [$params->trailingPercent] the percent to trail away from the current $market $price
             * @param {string} [$params->tpOrdKind] 'condition' or 'limit', the default is 'condition'
             * @param {bool} [$params->hedged] *swap and future only* true for hedged mode, false for one way mode
             * @param {string} [$params->marginMode] 'cross' or 'isolated', the default is 'cross'
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=$order-structure $order structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = $this->create_order_request($symbol, $type, $side, $amount, $price, $params);
            $method = $this->safe_string($this->options, 'createOrder', 'privatePostTradeBatchOrders');
            $requestOrdType = $this->safe_string($request, 'ordType');
            if (($requestOrdType === 'trigger') || ($requestOrdType === 'conditional') || ($requestOrdType === 'move_order_stop') || ($type === 'move_order_stop') || ($type === 'oco') || ($type === 'iceberg') || ($type === 'twap')) {
                $method = 'privatePostTradeOrderAlgo';
            }
            if (($method !== 'privatePostTradeOrder') && ($method !== 'privatePostTradeOrderAlgo') && ($method !== 'privatePostTradeBatchOrders')) {
                throw new ExchangeError($this->id . ' createOrder() $this->options["createOrder"] must be either privatePostTradeBatchOrders or privatePostTradeOrder or privatePostTradeOrderAlgo');
            }
            if ($method === 'privatePostTradeBatchOrders') {
                // keep the $request body the same
                // submit a single $order in an array to the batch $order endpoint
                // because it has a lower ratelimit
                $request = array( $request );
            }
            $response = null;
            if ($method === 'privatePostTradeOrder') {
                $response = Async\await($this->privatePostTradeOrder ($request));
            } elseif ($method === 'privatePostTradeOrderAlgo') {
                $response = Async\await($this->privatePostTradeOrderAlgo ($request));
            } else {
                $response = Async\await($this->privatePostTradeBatchOrders ($request));
            }
            $data = $this->safe_list($response, 'data', array());
            $first = $this->safe_dict($data, 0, array());
            $order = $this->parse_order($first, $market);
            $order['type'] = $type;
            $order['side'] = $side;
            return $order;
        }) ();
    }

    public function create_orders(array $orders, $params = array ()) {
        return Async\async(function () use ($orders, $params) {
            /**
             * create a list of trade $orders
             *
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-trade-post-place-multiple-$orders
             *
             * @param {Array} $orders list of $orders to create, each object should contain the parameters required by createOrder, namely symbol, $type, $side, $amount, $price and $params
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $ordersRequests = array();
            for ($i = 0; $i < count($orders); $i++) {
                $rawOrder = $orders[$i];
                $marketId = $this->safe_string($rawOrder, 'symbol');
                $type = $this->safe_string($rawOrder, 'type');
                $side = $this->safe_string($rawOrder, 'side');
                $amount = $this->safe_value($rawOrder, 'amount');
                $price = $this->safe_value($rawOrder, 'price');
                $orderParams = $this->safe_dict($rawOrder, 'params', array());
                $extendedParams = $this->extend($orderParams, $params); // the request does not accept extra $params since it's a list, so we're extending each order with the common $params
                $orderRequest = $this->create_order_request($marketId, $type, $side, $amount, $price, $extendedParams);
                $ordersRequests[] = $orderRequest;
            }
            $response = Async\await($this->privatePostTradeBatchOrders ($ordersRequests));
            // {
            //     "code" => "0",
            //     "data" => array(
            //        array(
            //           "clOrdId" => "e847386590ce4dBCc7f2a1b4c4509f82",
            //           "ordId" => "636305438765568000",
            //           "sCode" => "0",
            //           "sMsg" => "Order placed",
            //           "tag" => "e847386590ce4dBC"
            //        ),
            //        {
            //           "clOrdId" => "e847386590ce4dBC0b9993fe642d8f62",
            //           "ordId" => "636305438765568001",
            //           "sCode" => "0",
            //           "sMsg" => "Order placed",
            //           "tag" => "e847386590ce4dBC"
            //        }
            //     ),
            //     "inTime" => "1697979038584486",
            //     "msg" => "",
            //     "outTime" => "1697979038586493"
            // }
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_orders($data);
        }) ();
    }

    public function edit_order_request(string $id, $symbol, $type, $side, $amount = null, $price = null, $params = array ()) {
        $market = $this->market($symbol);
        $request = array(
            'instId' => $market['id'],
        );
        $isAlgoOrder = null;
        if (($type === 'trigger') || ($type === 'conditional') || ($type === 'move_order_stop') || ($type === 'oco') || ($type === 'iceberg') || ($type === 'twap')) {
            $isAlgoOrder = true;
        }
        $clientOrderId = $this->safe_string_2($params, 'clOrdId', 'clientOrderId');
        if ($clientOrderId !== null) {
            if ($isAlgoOrder) {
                $request['algoClOrdId'] = $clientOrderId;
            } else {
                $request['clOrdId'] = $clientOrderId;
            }
        } else {
            if ($isAlgoOrder) {
                $request['algoId'] = $id;
            } else {
                $request['ordId'] = $id;
            }
        }
        $stopLossTriggerPrice = $this->safe_value_2($params, 'stopLossPrice', 'newSlTriggerPx');
        $stopLossPrice = $this->safe_value($params, 'newSlOrdPx');
        $stopLossTriggerPriceType = $this->safe_string($params, 'newSlTriggerPxType', 'last');
        $takeProfitTriggerPrice = $this->safe_value_2($params, 'takeProfitPrice', 'newTpTriggerPx');
        $takeProfitPrice = $this->safe_value($params, 'newTpOrdPx');
        $takeProfitTriggerPriceType = $this->safe_string($params, 'newTpTriggerPxType', 'last');
        $stopLoss = $this->safe_value($params, 'stopLoss');
        $takeProfit = $this->safe_value($params, 'takeProfit');
        $stopLossDefined = ($stopLoss !== null);
        $takeProfitDefined = ($takeProfit !== null);
        if ($isAlgoOrder) {
            if (($stopLossTriggerPrice === null) && ($takeProfitTriggerPrice === null)) {
                throw new BadRequest($this->id . ' editOrder() requires a $stopLossPrice or $takeProfitPrice parameter for editing an algo order');
            }
            if ($stopLossTriggerPrice !== null) {
                if ($stopLossPrice === null) {
                    throw new BadRequest($this->id . ' editOrder() requires a newSlOrdPx parameter for editing an algo order');
                }
                $request['newSlTriggerPx'] = $this->price_to_precision($symbol, $stopLossTriggerPrice);
                $request['newSlOrdPx'] = ($type === 'market') ? '-1' : $this->price_to_precision($symbol, $stopLossPrice);
                $request['newSlTriggerPxType'] = $stopLossTriggerPriceType;
            }
            if ($takeProfitTriggerPrice !== null) {
                if ($takeProfitPrice === null) {
                    throw new BadRequest($this->id . ' editOrder() requires a newTpOrdPx parameter for editing an algo order');
                }
                $request['newTpTriggerPx'] = $this->price_to_precision($symbol, $takeProfitTriggerPrice);
                $request['newTpOrdPx'] = ($type === 'market') ? '-1' : $this->price_to_precision($symbol, $takeProfitPrice);
                $request['newTpTriggerPxType'] = $takeProfitTriggerPriceType;
            }
        } else {
            if ($stopLossTriggerPrice !== null) {
                $request['newSlTriggerPx'] = $this->price_to_precision($symbol, $stopLossTriggerPrice);
                $request['newSlOrdPx'] = ($type === 'market') ? '-1' : $this->price_to_precision($symbol, $stopLossPrice);
                $request['newSlTriggerPxType'] = $stopLossTriggerPriceType;
            }
            if ($takeProfitTriggerPrice !== null) {
                $request['newTpTriggerPx'] = $this->price_to_precision($symbol, $takeProfitTriggerPrice);
                $request['newTpOrdPx'] = ($type === 'market') ? '-1' : $this->price_to_precision($symbol, $takeProfitPrice);
                $request['newTpTriggerPxType'] = $takeProfitTriggerPriceType;
            }
            if ($stopLossDefined) {
                $stopLossTriggerPrice = $this->safe_value($stopLoss, 'triggerPrice');
                $stopLossPrice = $this->safe_value($stopLoss, 'price');
                $stopLossType = $this->safe_string($stopLoss, 'type');
                $request['newSlTriggerPx'] = $this->price_to_precision($symbol, $stopLossTriggerPrice);
                $request['newSlOrdPx'] = ($stopLossType === 'market') ? '-1' : $this->price_to_precision($symbol, $stopLossPrice);
                $request['newSlTriggerPxType'] = $stopLossTriggerPriceType;
            }
            if ($takeProfitDefined) {
                $takeProfitTriggerPrice = $this->safe_value($takeProfit, 'triggerPrice');
                $takeProfitPrice = $this->safe_value($takeProfit, 'price');
                $takeProfitType = $this->safe_string($takeProfit, 'type');
                $request['newTpOrdKind'] = ($takeProfitType === 'limit') ? $takeProfitType : 'condition';
                $request['newTpTriggerPx'] = $this->price_to_precision($symbol, $takeProfitTriggerPrice);
                $request['newTpOrdPx'] = ($takeProfitType === 'market') ? '-1' : $this->price_to_precision($symbol, $takeProfitPrice);
                $request['newTpTriggerPxType'] = $takeProfitTriggerPriceType;
            }
        }
        if ($amount !== null) {
            $request['newSz'] = $this->amount_to_precision($symbol, $amount);
        }
        if (!$isAlgoOrder) {
            if ($price !== null) {
                $request['newPx'] = $this->price_to_precision($symbol, $price);
            }
        }
        $params = $this->omit($params, array( 'clOrdId', 'clientOrderId', 'takeProfitPrice', 'stopLossPrice', 'stopLoss', 'takeProfit', 'postOnly' ));
        return $this->extend($request, $params);
    }

    public function edit_order(string $id, string $symbol, string $type, string $side, ?float $amount = null, ?float $price = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $type, $side, $amount, $price, $params) {
            /**
             * edit a trade $order
             *
             * @see https://www.okx.com/docs-v5/en/#$order-book-trading-trade-post-amend-$order
             * @see https://www.okx.com/docs-v5/en/#$order-book-trading-algo-trading-post-amend-algo-$order
             *
             * @param {string} $id $order $id
             * @param {string} $symbol unified $symbol of the $market to create an $order in
             * @param {string} $type 'market' or 'limit'
             * @param {string} $side 'buy' or 'sell'
             * @param {float} $amount how much of the currency you want to trade in units of the base currency
             * @param {float} [$price] the $price at which the $order is to be fulfilled, in units of the quote currency, ignored in $market orders
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->clientOrderId] client $order $id, uses $id if not passed
             * @param {float} [$params->stopLossPrice] stop loss trigger $price
             * @param {float} [$params->newSlOrdPx] the stop loss $order $price, set to stopLossPrice if the $type is $market
             * @param {string} [$params->newSlTriggerPxType] 'last', 'index' or 'mark' used to specify the stop loss trigger $price $type, default is 'last'
             * @param {float} [$params->takeProfitPrice] take profit trigger $price
             * @param {float} [$params->newTpOrdPx] the take profit $order $price, set to takeProfitPrice if the $type is $market
             * @param {string} [$params->newTpTriggerPxType] 'last', 'index' or 'mark' used to specify the take profit trigger $price $type, default is 'last'
             * @param {array} [$params->stopLoss] *stopLoss object in $params* containing the triggerPrice at which the attached stop loss $order will be triggered
             * @param {float} [$params->stopLoss.triggerPrice] stop loss trigger $price
             * @param {float} [$params->stopLoss.price] used for stop loss limit orders, not used for stop loss $market $price orders
             * @param {string} [$params->stopLoss.type] 'market' or 'limit' used to specify the stop loss $price $type
             * @param {array} [$params->takeProfit] *takeProfit object in $params* containing the triggerPrice at which the attached take profit $order will be triggered
             * @param {float} [$params->takeProfit.triggerPrice] take profit trigger $price
             * @param {float} [$params->takeProfit.price] used for take profit limit orders, not used for take profit $market $price orders
             * @param {string} [$params->takeProfit.type] 'market' or 'limit' used to specify the take profit $price $type
             * @param {string} [$params->newTpOrdKind] 'condition' or 'limit', the default is 'condition'
             * @return {array} an ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = $this->edit_order_request($id, $symbol, $type, $side, $amount, $price, $params);
            $isAlgoOrder = null;
            if (($type === 'trigger') || ($type === 'conditional') || ($type === 'move_order_stop') || ($type === 'oco') || ($type === 'iceberg') || ($type === 'twap')) {
                $isAlgoOrder = true;
            }
            $response = null;
            if ($isAlgoOrder) {
                $response = Async\await($this->privatePostTradeAmendAlgos ($this->extend($request, $params)));
            } else {
                $response = Async\await($this->privatePostTradeAmendOrder ($this->extend($request, $params)));
            }
            //
            //     {
            //        "code" => "0",
            //        "data" => array(
            //            {
            //                 "clOrdId" => "e847386590ce4dBCc1a045253497a547",
            //                 "ordId" => "559176536793178112",
            //                 "reqId" => "",
            //                 "sCode" => "0",
            //                 "sMsg" => ""
            //            }
            //        ),
            //        "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $first = $this->safe_dict($data, 0, array());
            $order = $this->parse_order($first, $market);
            $order['type'] = $type;
            $order['side'] = $side;
            return $order;
        }) ();
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $params) {
            /**
             * cancels an open $order
             *
             * @see https://www.okx.com/docs-v5/en/#$order-book-trading-trade-post-cancel-$order
             * @see https://www.okx.com/docs-v5/en/#$order-book-trading-algo-trading-post-cancel-algo-$order
             *
             * @param {string} $id $order $id
             * @param {string} $symbol unified $symbol of the $market the $order was made in
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->trigger] true if $trigger orders
             * @param {boolean} [$params->trailing] set to true if you want to cancel a $trailing $order
             * @return {array} An ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structure~
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' cancelOrder() requires a $symbol argument');
            }
            $trigger = $this->safe_value_2($params, 'stop', 'trigger');
            $trailing = $this->safe_bool($params, 'trailing', false);
            if ($trigger || $trailing) {
                $orderInner = Async\await($this->cancel_orders(array( $id ), $symbol, $params));
                return $this->safe_dict($orderInner, 0);
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'instId' => $market['id'],
                // 'ordId' => $id, // either ordId or clOrdId is required
                // 'clOrdId' => $clientOrderId,
            );
            $clientOrderId = $this->safe_string_2($params, 'clOrdId', 'clientOrderId');
            if ($clientOrderId !== null) {
                $request['clOrdId'] = $clientOrderId;
            } else {
                $request['ordId'] = $id;
            }
            $query = $this->omit($params, array( 'clOrdId', 'clientOrderId' ));
            $response = Async\await($this->privatePostTradeCancelOrder ($this->extend($request, $query)));
            // array("code":"0","data":[array("clOrdId":"","ordId":"317251910906576896","sCode":"0","sMsg":"")],"msg":"")
            $data = $this->safe_value($response, 'data', array());
            $order = $this->safe_dict($data, 0);
            return $this->parse_order($order, $market);
        }) ();
    }

    public function parse_ids($ids) {
        /**
         * @ignore
         * @param {string[]|string} $ids order $ids
         * @return {string[]} list of order $ids
         */
        if (($ids !== null) && gettype($ids) === 'string') {
            return explode(',', $ids);
        } else {
            return $ids;
        }
    }

    public function cancel_orders($ids, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($ids, $symbol, $params) {
            /**
             * cancel multiple orders
             *
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-trade-post-cancel-multiple-orders
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-algo-trading-post-cancel-algo-order
             *
             * @param {string[]} $ids order $ids
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->trigger] whether the order is a stop/trigger order
             * @param {boolean} [$params->trailing] set to true if you want to cancel $trailing orders
             * @return {array} an list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            // TODO : the original endpoint signature differs, according to that you can skip individual $symbol and assign $ids in batch. At this moment, `$params` is not being used too.
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' cancelOrders() requires a $symbol argument');
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array();
            $options = $this->safe_value($this->options, 'cancelOrders', array());
            $defaultMethod = $this->safe_string($options, 'method', 'privatePostTradeCancelBatchOrders');
            $method = $this->safe_string($params, 'method', $defaultMethod);
            $clientOrderIds = $this->parse_ids($this->safe_value_2($params, 'clOrdId', 'clientOrderId'));
            $algoIds = $this->parse_ids($this->safe_value($params, 'algoId'));
            $trigger = $this->safe_value_2($params, 'stop', 'trigger');
            $trailing = $this->safe_bool($params, 'trailing', false);
            if ($trigger || $trailing) {
                $method = 'privatePostTradeCancelAlgos';
            }
            if ($clientOrderIds === null) {
                $ids = $this->parse_ids($ids);
                if ($algoIds !== null) {
                    for ($i = 0; $i < count($algoIds); $i++) {
                        $request[] = array(
                            'algoId' => $algoIds[$i],
                            'instId' => $market['id'],
                        );
                    }
                }
                for ($i = 0; $i < count($ids); $i++) {
                    if ($trailing || $trigger) {
                        $request[] = array(
                            'algoId' => $ids[$i],
                            'instId' => $market['id'],
                        );
                    } else {
                        $request[] = array(
                            'ordId' => $ids[$i],
                            'instId' => $market['id'],
                        );
                    }
                }
            } else {
                for ($i = 0; $i < count($clientOrderIds); $i++) {
                    if ($trailing || $trigger) {
                        $request[] = array(
                            'instId' => $market['id'],
                            'algoClOrdId' => $clientOrderIds[$i],
                        );
                    } else {
                        $request[] = array(
                            'instId' => $market['id'],
                            'clOrdId' => $clientOrderIds[$i],
                        );
                    }
                }
            }
            $response = null;
            if ($method === 'privatePostTradeCancelAlgos') {
                $response = Async\await($this->privatePostTradeCancelAlgos ($request)); // * dont extend with $params, otherwise ARRAY will be turned into OBJECT
            } else {
                $response = Async\await($this->privatePostTradeCancelBatchOrders ($request)); // * dont extend with $params, otherwise ARRAY will be turned into OBJECT
            }
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             array(
            //                 "clOrdId" => "e123456789ec4dBC1123456ba123b45e",
            //                 "ordId" => "405071912345641543",
            //                 "sCode" => "0",
            //                 "sMsg" => ""
            //             ),
            //             ...
            //         ),
            //         "msg" => ""
            //     }
            //
            // Algo order
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "algoId" => "431375349042380800",
            //                 "sCode" => "0",
            //                 "sMsg" => ""
            //             }
            //         ),
            //         "msg" => ""
            //     }
            //
            $ordersData = $this->safe_list($response, 'data', array());
            return $this->parse_orders($ordersData, $market, null, null, $params);
        }) ();
    }

    public function cancel_orders_for_symbols(array $orders, $params = array ()) {
        return Async\async(function () use ($orders, $params) {
            /**
             * cancel multiple $orders for multiple symbols
             *
             * @see https://www.okx.com/docs-v5/en/#$order-book-trading-trade-post-cancel-multiple-$orders
             * @see https://www.okx.com/docs-v5/en/#$order-book-trading-algo-trading-post-cancel-algo-$order
             *
             * @param {CancellationRequest[]} $orders each $order should contain the parameters required by cancelOrder namely $id and $symbol, example [array("id" => "a", "symbol" => "BTC/USDT"), array("id" => "b", "symbol" => "ETH/USDT")]
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->trigger] whether the $order is a stop/trigger $order
             * @param {boolean} [$params->trailing] set to true if you want to cancel $trailing $orders
             * @return {array} an list of ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structures~
             */
            Async\await($this->load_markets());
            $request = array();
            $options = $this->safe_dict($this->options, 'cancelOrders', array());
            $defaultMethod = $this->safe_string($options, 'method', 'privatePostTradeCancelBatchOrders');
            $method = $this->safe_string($params, 'method', $defaultMethod);
            $trigger = $this->safe_bool_2($params, 'stop', 'trigger');
            $trailing = $this->safe_bool($params, 'trailing', false);
            $isStopOrTrailing = $trigger || $trailing;
            if ($isStopOrTrailing) {
                $method = 'privatePostTradeCancelAlgos';
            }
            for ($i = 0; $i < count($orders); $i++) {
                $order = $orders[$i];
                $id = $this->safe_string($order, 'id');
                $clientOrderId = $this->safe_string_2($order, 'clOrdId', 'clientOrderId');
                $symbol = $this->safe_string($order, 'symbol');
                $market = $this->market($symbol);
                $idKey = 'ordId';
                if ($isStopOrTrailing) {
                    $idKey = 'algoId';
                } elseif ($clientOrderId !== null) {
                    if ($isStopOrTrailing) {
                        $idKey = 'algoClOrdId';
                    } else {
                        $idKey = 'clOrdId';
                    }
                }
                $requestItem = array(
                    'instId' => $market['id'],
                );
                $requestItem[$idKey] = ($clientOrderId !== null) ? $clientOrderId : $id;
                $request[] = $requestItem;
            }
            $response = null;
            if ($method === 'privatePostTradeCancelAlgos') {
                $response = Async\await($this->privatePostTradeCancelAlgos ($request)); // * dont extend with $params, otherwise ARRAY will be turned into OBJECT
            } else {
                $response = Async\await($this->privatePostTradeCancelBatchOrders ($request)); // * dont extend with $params, otherwise ARRAY will be turned into OBJECT
            }
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             array(
            //                 "clOrdId" => "e123456789ec4dBC1123456ba123b45e",
            //                 "ordId" => "405071912345641543",
            //                 "sCode" => "0",
            //                 "sMsg" => ""
            //             ),
            //             ...
            //         ),
            //         "msg" => ""
            //     }
            //
            // Algo $order
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "algoId" => "431375349042380800",
            //                 "sCode" => "0",
            //                 "sMsg" => ""
            //             }
            //         ),
            //         "msg" => ""
            //     }
            //
            $ordersData = $this->safe_list($response, 'data', array());
            return $this->parse_orders($ordersData, null, null, null, $params);
        }) ();
    }

    public function cancel_all_orders_after(?int $timeout, $params = array ()) {
        return Async\async(function () use ($timeout, $params) {
            /**
             * dead man's switch, cancel all orders after the given $timeout
             *
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-trade-post-cancel-all-after
             *
             * @param {number} $timeout time in milliseconds, 0 represents cancel the timer
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} the api result
             */
            Async\await($this->load_markets());
            $request = array(
                'timeOut' => ($timeout > 0) ? $this->parse_to_int($timeout / 1000) : 0,
            );
            $response = Async\await($this->privatePostTradeCancelAllAfter ($this->extend($request, $params)));
            //
            //     {
            //         "code":"0",
            //         "msg":"",
            //         "data":array(
            //             {
            //                 "triggerTime":"1587971460",
            //                 "ts":"1587971400"
            //             }
            //         )
            //     }
            //
            return $response;
        }) ();
    }

    public function parse_order_status(?string $status) {
        $statuses = array(
            'canceled' => 'canceled',
            'order_failed' => 'canceled',
            'live' => 'open',
            'partially_filled' => 'open',
            'filled' => 'closed',
            'effective' => 'closed',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        // createOrder
        //
        //     {
        //         "clOrdId" => "oktswap6",
        //         "ordId" => "312269865356374016",
        //         "tag" => "",
        //         "sCode" => "0",
        //         "sMsg" => ""
        //     }
        //
        // editOrder
        //
        //     {
        //         "clOrdId" => "e847386590ce4dBCc1a045253497a547",
        //         "ordId" => "559176536793178112",
        //         "reqId" => "",
        //         "sCode" => "0",
        //         "sMsg" => ""
        //     }
        //
        // Spot and Swap fetchOrder, fetchOpenOrders
        //
        //     {
        //         "accFillSz" => "0",
        //         "avgPx" => "",
        //         "cTime" => "1621910749815",
        //         "category" => "normal",
        //         "ccy" => "",
        //         "clOrdId" => "",
        //         "fee" => "0",
        //         "feeCcy" => "ETH",
        //         "fillPx" => "",
        //         "fillSz" => "0",
        //         "fillTime" => "",
        //         "instId" => "ETH-USDT",
        //         "instType" => "SPOT",
        //         "lever" => "",
        //         "ordId" => "317251910906576896",
        //         "ordType" => "limit",
        //         "pnl" => "0",
        //         "posSide" => "net",
        //         "px" => "2000",
        //         "rebate" => "0",
        //         "rebateCcy" => "USDT",
        //         "side" => "buy",
        //         "slOrdPx" => "",
        //         "slTriggerPx" => "",
        //         "state" => "live",
        //         "sz" => "0.001",
        //         "tag" => "",
        //         "tdMode" => "cash",
        //         "tpOrdPx" => "",
        //         "tpTriggerPx" => "",
        //         "tradeId" => "",
        //         "uTime" => "1621910749815"
        //     }
        //
        // watchOrders & fetchClosedOrders
        //
        //    {
        //        "algoClOrdId" => "",
        //        "algoId" => "",
        //        "attachAlgoClOrdId" => "",
        //        "attachAlgoOrds" => array(),
        //        "cancelSource" => "",
        //        "cancelSourceReason" => "", // not present in WS, but present in fetchClosedOrders
        //        "category" => "normal",
        //        "ccy" => "", // empty in WS, but eg. `USDT` in fetchClosedOrders
        //        "clOrdId" => "",
        //        "cTime" => "1751705801423",
        //        "feeCcy" => "USDT",
        //        "instId" => "LINK-USDT-SWAP",
        //        "instType" => "SWAP",
        //        "isTpLimit" => "false",
        //        "lever" => "3",
        //        "linkedAlgoOrd" => array( "algoId" => "" ),
        //        "ordId" => "2657625147249614848",
        //        "ordType" => "limit",
        //        "posSide" => "net",
        //        "px" => "13.142",
        //        "pxType" => "",
        //        "pxUsd" => "",
        //        "pxVol" => "",
        //        "quickMgnType" => "",
        //        "rebate" => "0",
        //        "rebateCcy" => "USDT",
        //        "reduceOnly" => "true",
        //        "side" => "sell",
        //        "slOrdPx" => "",
        //        "slTriggerPx" => "",
        //        "slTriggerPxType" => "",
        //        "source" => "",
        //        "stpId" => "",
        //        "stpMode" => "cancel_maker",
        //        "sz" => "0.1",
        //        "tag" => "",
        //        "tdMode" => "isolated",
        //        "tgtCcy" => "",
        //        "tpOrdPx" => "",
        //        "tpTriggerPx" => "",
        //        "tpTriggerPxType" => "",
        //        "uTime" => "1751705807467",
        //        "reqId" => "",                      // field present only in WS
        //        "msg" => "",                        // field present only in WS
        //        "amendResult" => "",                // field present only in WS
        //        "amendSource" => "",                // field present only in WS
        //        "code" => "0",                      // field present only in WS
        //        "fillFwdPx" => "",                  // field present only in WS
        //        "fillMarkVol" => "",                // field present only in WS
        //        "fillPxUsd" => "",                  // field present only in WS
        //        "fillPxVol" => "",                  // field present only in WS
        //        "lastPx" => "13.142",               // field present only in WS
        //        "notionalUsd" => "1.314515408",     // field present only in WS
        //
        //     #### these below fields are empty on first omit from websocket, because of "creation" event. however, if $order is executed, it also immediately sends another update with these fields $filled  ###
        //
        //        "pnl" => "-0.0001",
        //        "accFillSz" => "0.1",
        //        "avgPx" => "13.142",
        //        "state" => "filled",
        //        "fee" => "-0.00026284",
        //        "fillPx" => "13.142",
        //        "tradeId" => "293429690",
        //        "fillSz" => "0.1",
        //        "fillTime" => "1751705807467",
        //        "fillNotionalUsd" => "1.314515408", // field present only in WS
        //        "fillPnl" => "-0.0001",             // field present only in WS
        //        "fillFee" => "-0.00026284",         // field present only in WS
        //        "fillFeeCcy" => "USDT",             // field present only in WS
        //        "execType" => "M",                  // field present only in WS
        //        "fillMarkPx" => "13.141",           // field present only in WS
        //        "fillIdxPx" => "13.147"             // field present only in WS
        //    }
        //
        //
        // Algo Order fetchOpenOrders, fetchCanceledOrders, fetchClosedOrders
        //
        //     {
        //         "activePx" => "",
        //         "activePxType" => "",
        //         "actualPx" => "",
        //         "actualSide" => "buy",
        //         "actualSz" => "0",
        //         "algoId" => "431375349042380800",
        //         "cTime" => "1649119897778",
        //         "callbackRatio" => "",
        //         "callbackSpread" => "",
        //         "ccy" => "",
        //         "ctVal" => "0.01",
        //         "instId" => "BTC-USDT-SWAP",
        //         "instType" => "SWAP",
        //         "last" => "46538.9",
        //         "lever" => "125",
        //         "moveTriggerPx" => "",
        //         "notionalUsd" => "467.059",
        //         "ordId" => "",
        //         "ordPx" => "50000",
        //         "ordType" => "trigger",
        //         "posSide" => "long",
        //         "pxLimit" => "",
        //         "pxSpread" => "",
        //         "pxVar" => "",
        //         "side" => "buy",
        //         "slOrdPx" => "",
        //         "slTriggerPx" => "",
        //         "slTriggerPxType" => "",
        //         "state" => "live",
        //         "sz" => "1",
        //         "szLimit" => "",
        //         "tag" => "",
        //         "tdMode" => "isolated",
        //         "tgtCcy" => "",
        //         "timeInterval" => "",
        //         "tpOrdPx" => "",
        //         "tpTriggerPx" => "",
        //         "tpTriggerPxType" => "",
        //         "triggerPx" => "50000",
        //         "triggerPxType" => "last",
        //         "triggerTime" => "",
        //         "uly" => "BTC-USDT"
        //     }
        //
        $scode = $this->safe_string($order, 'sCode');
        if (($scode !== null) && ($scode !== '0')) {
            return $this->safe_order(array(
                'id' => $this->safe_string($order, 'ordId'),
                'clientOrderId' => $this->safe_string($order, 'clOrdId'),
                'status' => 'rejected',
                'info' => $order,
            ));
        }
        $id = $this->safe_string_2($order, 'algoId', 'ordId');
        $timestamp = $this->safe_integer($order, 'cTime');
        $lastUpdateTimestamp = $this->safe_integer($order, 'uTime');
        $lastTradeTimestamp = $this->safe_integer($order, 'fillTime');
        $side = $this->safe_string($order, 'side');
        $type = $this->safe_string($order, 'ordType');
        $postOnly = null;
        $timeInForce = null;
        if ($type === 'post_only') {
            $postOnly = true;
            $type = 'limit';
        } elseif ($type === 'fok') {
            $timeInForce = 'FOK';
            $type = 'limit';
        } elseif ($type === 'ioc') {
            $timeInForce = 'IOC';
            $type = 'limit';
        }
        $marketId = $this->safe_string($order, 'instId');
        $market = $this->safe_market($marketId, $market);
        $symbol = $this->safe_symbol($marketId, $market, '-');
        $filled = $this->safe_string($order, 'accFillSz');
        $price = $this->safe_string_2($order, 'px', 'ordPx');
        $average = $this->safe_string($order, 'avgPx');
        $status = $this->parse_order_status($this->safe_string($order, 'state'));
        $feeCostString = $this->safe_string($order, 'fee');
        $amount = null;
        $cost = null;
        // spot $market buy => "sz" can refer either to base currency units or to quote currency units
        // see documentation => https://www.okx.com/docs-v5/en/#rest-api-trade-place-$order
        $defaultTgtCcy = $this->safe_string($this->options, 'tgtCcy', 'base_ccy');
        $tgtCcy = $this->safe_string($order, 'tgtCcy', $defaultTgtCcy);
        $instType = $this->safe_string($order, 'instType');
        if (($side === 'buy') && ($type === 'market') && ($instType === 'SPOT') && ($tgtCcy === 'quote_ccy')) {
            // "sz" refers to the $cost
            $cost = $this->safe_string($order, 'sz');
        } else {
            // "sz" refers to the trade currency $amount
            $amount = $this->safe_string($order, 'sz');
        }
        $fee = null;
        if ($feeCostString !== null) {
            $feeCostSigned = Precise::string_neg($feeCostString);
            $feeCurrencyId = $this->safe_string($order, 'feeCcy');
            $feeCurrencyCode = $this->safe_currency_code($feeCurrencyId);
            $fee = array(
                'cost' => $this->parse_number($feeCostSigned),
                'currency' => $feeCurrencyCode,
            );
        }
        $clientOrderId = $this->safe_string($order, 'clOrdId');
        if (($clientOrderId !== null) && (strlen($clientOrderId) < 1)) {
            $clientOrderId = null; // fix empty $clientOrderId string
        }
        $stopLossPrice = $this->safe_number_2($order, 'slTriggerPx', 'slOrdPx');
        $takeProfitPrice = $this->safe_number_2($order, 'tpTriggerPx', 'tpOrdPx');
        $reduceOnlyRaw = $this->safe_string($order, 'reduceOnly');
        $reduceOnly = false;
        if ($reduceOnly !== null) {
            $reduceOnly = ($reduceOnlyRaw === 'true');
        }
        return $this->safe_order(array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => $clientOrderId,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => $lastTradeTimestamp,
            'lastUpdateTimestamp' => $lastUpdateTimestamp,
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => $timeInForce,
            'postOnly' => $postOnly,
            'side' => $side,
            'price' => $price,
            'stopLossPrice' => $stopLossPrice,
            'takeProfitPrice' => $takeProfitPrice,
            'triggerPrice' => $this->safe_number_n($order, array( 'triggerPx', 'moveTriggerPx' )),
            'average' => $average,
            'cost' => $cost,
            'amount' => $amount,
            'filled' => $filled,
            'remaining' => null,
            'status' => $status,
            'fee' => $fee,
            'trades' => null,
            'reduceOnly' => $reduceOnly,
        ), $market);
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $params) {
            /**
             * fetch an $order by the $id
             *
             * @see https://www.okx.com/docs-v5/en/#$order-book-trading-trade-get-$order-details
             * @see https://www.okx.com/docs-v5/en/#$order-book-trading-algo-trading-get-algo-$order-details
             *
             * @param {string} $id the $order $id
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra and exchange specific parameters
             * @param {boolean} [$params->trigger] true if fetching $trigger orders
             * @return ~@link https://docs.ccxt.com/#/?$id=$order-structure an $order structure~
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' fetchOrder() requires a $symbol argument');
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'instId' => $market['id'],
                // 'clOrdId' => 'abcdef12345', // optional, [a-z0-9]array(1,32)
                // 'ordId' => $id,
                // 'instType' => // spot, swap, futures, margin
            );
            $clientOrderId = $this->safe_string_2($params, 'clOrdId', 'clientOrderId');
            $options = $this->safe_value($this->options, 'fetchOrder', array());
            $defaultMethod = $this->safe_string($options, 'method', 'privateGetTradeOrder');
            $method = $this->safe_string($params, 'method', $defaultMethod);
            $trigger = $this->safe_value_2($params, 'stop', 'trigger');
            if ($trigger) {
                $method = 'privateGetTradeOrderAlgo';
                if ($clientOrderId !== null) {
                    $request['algoClOrdId'] = $clientOrderId;
                } else {
                    $request['algoId'] = $id;
                }
            } else {
                if ($clientOrderId !== null) {
                    $request['clOrdId'] = $clientOrderId;
                } else {
                    $request['ordId'] = $id;
                }
            }
            $query = $this->omit($params, array( 'method', 'clOrdId', 'clientOrderId', 'stop', 'trigger' ));
            $response = null;
            if ($method === 'privateGetTradeOrderAlgo') {
                $response = Async\await($this->privateGetTradeOrderAlgo ($this->extend($request, $query)));
            } else {
                $response = Async\await($this->privateGetTradeOrder ($this->extend($request, $query)));
            }
            //
            // Spot and Swap
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "accFillSz" => "0",
            //                 "avgPx" => "",
            //                 "cTime" => "1621910749815",
            //                 "category" => "normal",
            //                 "ccy" => "",
            //                 "clOrdId" => "",
            //                 "fee" => "0",
            //                 "feeCcy" => "ETH",
            //                 "fillPx" => "",
            //                 "fillSz" => "0",
            //                 "fillTime" => "",
            //                 "instId" => "ETH-USDT",
            //                 "instType" => "SPOT",
            //                 "lever" => "",
            //                 "ordId" => "317251910906576896",
            //                 "ordType" => "limit",
            //                 "pnl" => "0",
            //                 "posSide" => "net",
            //                 "px":"20 00",
            //                 "rebate" => "0",
            //                 "rebateCcy" => "USDT",
            //                 "side" => "buy",
            //                 "slOrdPx" => "",
            //                 "slTriggerPx" => "",
            //                 "state" => "live",
            //                 "sz":"0. 001",
            //                 "tag" => "",
            //                 "tdMode" => "cash",
            //                 "tpOrdPx" => "",
            //                 "tpTriggerPx" => "",
            //                 "tradeId" => "",
            //                 "uTime" => "1621910749815"
            //             }
            //         ),
            //         "msg" => ""
            //     }
            //
            // Algo $order
            //     {
            //         "code":"0",
            //         "msg":"",
            //         "data":array(
            //             {
            //                 "instType":"FUTURES",
            //                 "instId":"BTC-USD-200329",
            //                 "ordId":"123445",
            //                 "ccy":"BTC",
            //                 "clOrdId":"",
            //                 "algoId":"1234",
            //                 "sz":"999",
            //                 "closeFraction":"",
            //                 "ordType":"oco",
            //                 "side":"buy",
            //                 "posSide":"long",
            //                 "tdMode":"cross",
            //                 "tgtCcy" => "",
            //                 "state":"effective",
            //                 "lever":"20",
            //                 "tpTriggerPx":"",
            //                 "tpTriggerPxType":"",
            //                 "tpOrdPx":"",
            //                 "slTriggerPx":"",
            //                 "slTriggerPxType":"",
            //                 "triggerPx":"99",
            //                 "triggerPxType":"last",
            //                 "ordPx":"12",
            //                 "actualSz":"",
            //                 "actualPx":"",
            //                 "actualSide":"",
            //                 "pxVar":"",
            //                 "pxSpread":"",
            //                 "pxLimit":"",
            //                 "szLimit":"",
            //                 "tag" => "adadadadad",
            //                 "timeInterval":"",
            //                 "callbackRatio":"",
            //                 "callbackSpread":"",
            //                 "activePx":"",
            //                 "moveTriggerPx":"",
            //                 "reduceOnly" => "false",
            //                 "triggerTime":"1597026383085",
            //                 "last" => "16012",
            //                 "failCode" => "",
            //                 "algoClOrdId" => "",
            //                 "cTime":"1597026383000"
            //             }
            //         )
            //     }
            //
            $data = $this->safe_value($response, 'data', array());
            $order = $this->safe_dict($data, 0);
            return $this->parse_order($order, $market);
        }) ();
    }

    public function fetch_open_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetch all unfilled currently open orders
             *
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-trade-get-order-list
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-algo-trading-get-algo-order-list
             *
             * @param {string} $symbol unified $market $symbol
             * @param {int} [$since] the earliest time in ms to fetch open orders for
             * @param {int} [$limit] the maximum number of  open orders structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {bool} [$params->trigger] True if fetching $trigger or conditional orders
             * @param {string} [$params->ordType] "conditional", "oco", "trigger", "move_order_stop", "iceberg", or "twap"
             * @param {string} [$params->algoId] Algo ID "'433845797218942976'"
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @param {boolean} [$params->trailing] set to true if you want to fetch $trailing orders
             * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchOpenOrders', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_dynamic('fetchOpenOrders', $symbol, $since, $limit, $params));
            }
            $request = array(
                // 'instType' => 'SPOT', // SPOT, MARGIN, SWAP, FUTURES, OPTION
                // 'uly' => currency['id'],
                // 'instId' => $market['id'],
                // 'ordType' => 'limit', // $market, $limit, post_only, fok, ioc, comma-separated, stop orders => conditional, oco, $trigger, move_order_stop, iceberg, or twap
                // 'state' => 'live', // live, partially_filled
                // 'after' => orderId,
                // 'before' => orderId,
                // 'limit' => $limit, // default 100, max 100
            );
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['instId'] = $market['id'];
            }
            if ($limit !== null) {
                $request['limit'] = $limit; // default 100, max 100
            }
            $options = $this->safe_value($this->options, 'fetchOpenOrders', array());
            $algoOrderTypes = $this->safe_value($this->options, 'algoOrderTypes', array());
            $defaultMethod = $this->safe_string($options, 'method', 'privateGetTradeOrdersPending');
            $method = $this->safe_string($params, 'method', $defaultMethod);
            $ordType = $this->safe_string($params, 'ordType');
            $trigger = $this->safe_value_2($params, 'stop', 'trigger');
            $trailing = $this->safe_bool($params, 'trailing', false);
            if ($trailing || $trigger || (is_array($algoOrderTypes) && array_key_exists($ordType, $algoOrderTypes))) {
                $method = 'privateGetTradeOrdersAlgoPending';
            }
            if ($trailing) {
                $request['ordType'] = 'move_order_stop';
            } elseif ($trigger && ($ordType === null)) {
                $request['ordType'] = 'trigger';
            }
            $query = $this->omit($params, array( 'method', 'stop', 'trigger', 'trailing' ));
            $response = null;
            if ($method === 'privateGetTradeOrdersAlgoPending') {
                $response = Async\await($this->privateGetTradeOrdersAlgoPending ($this->extend($request, $query)));
            } else {
                $response = Async\await($this->privateGetTradeOrdersPending ($this->extend($request, $query)));
            }
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "accFillSz" => "0",
            //                 "avgPx" => "",
            //                 "cTime" => "1621910749815",
            //                 "category" => "normal",
            //                 "ccy" => "",
            //                 "clOrdId" => "",
            //                 "fee" => "0",
            //                 "feeCcy" => "ETH",
            //                 "fillPx" => "",
            //                 "fillSz" => "0",
            //                 "fillTime" => "",
            //                 "instId" => "ETH-USDT",
            //                 "instType" => "SPOT",
            //                 "lever" => "",
            //                 "ordId" => "317251910906576896",
            //                 "ordType" => "limit",
            //                 "pnl" => "0",
            //                 "posSide" => "net",
            //                 "px":"20 00",
            //                 "rebate" => "0",
            //                 "rebateCcy" => "USDT",
            //                 "side" => "buy",
            //                 "slOrdPx" => "",
            //                 "slTriggerPx" => "",
            //                 "state" => "live",
            //                 "sz":"0. 001",
            //                 "tag" => "",
            //                 "tdMode" => "cash",
            //                 "tpOrdPx" => "",
            //                 "tpTriggerPx" => "",
            //                 "tradeId" => "",
            //                 "uTime" => "1621910749815"
            //             }
            //         ),
            //         "msg":""
            //     }
            //
            // Algo order
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "activePx" => "",
            //                 "activePxType" => "",
            //                 "actualPx" => "",
            //                 "actualSide" => "buy",
            //                 "actualSz" => "0",
            //                 "algoId" => "431375349042380800",
            //                 "cTime" => "1649119897778",
            //                 "callbackRatio" => "",
            //                 "callbackSpread" => "",
            //                 "ccy" => "",
            //                 "ctVal" => "0.01",
            //                 "instId" => "BTC-USDT-SWAP",
            //                 "instType" => "SWAP",
            //                 "last" => "46538.9",
            //                 "lever" => "125",
            //                 "moveTriggerPx" => "",
            //                 "notionalUsd" => "467.059",
            //                 "ordId" => "",
            //                 "ordPx" => "50000",
            //                 "ordType" => "trigger",
            //                 "posSide" => "long",
            //                 "pxLimit" => "",
            //                 "pxSpread" => "",
            //                 "pxVar" => "",
            //                 "side" => "buy",
            //                 "slOrdPx" => "",
            //                 "slTriggerPx" => "",
            //                 "slTriggerPxType" => "",
            //                 "state" => "live",
            //                 "sz" => "1",
            //                 "szLimit" => "",
            //                 "tag" => "",
            //                 "tdMode" => "isolated",
            //                 "tgtCcy" => "",
            //                 "timeInterval" => "",
            //                 "tpOrdPx" => "",
            //                 "tpTriggerPx" => "",
            //                 "tpTriggerPxType" => "",
            //                 "triggerPx" => "50000",
            //                 "triggerPxType" => "last",
            //                 "triggerTime" => "",
            //                 "uly" => "BTC-USDT"
            //             }
            //         ),
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_orders($data, $market, $since, $limit);
        }) ();
    }

    public function fetch_canceled_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetches information on multiple canceled orders made by the user
             *
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-trade-get-order-history-last-7-days
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-algo-trading-get-algo-order-history
             *
             * @param {string} $symbol unified $market $symbol of the $market orders were made in
             * @param {int} [$since] timestamp in ms of the earliest order, default is null
             * @param {int} [$limit] max number of orders to return, default is null
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {bool} [$params->trigger] True if fetching $trigger or conditional orders
             * @param {string} [$params->ordType] "conditional", "oco", "trigger", "move_order_stop", "iceberg", or "twap"
             * @param {string} [$params->algoId] Algo ID "'433845797218942976'"
             * @param {int} [$params->until] timestamp in ms to fetch orders for
             * @param {boolean} [$params->trailing] set to true if you want to fetch $trailing orders
             * @return {array} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $request = array(
                // 'instType' => strtoupper($type), // SPOT, MARGIN, SWAP, FUTURES, OPTION
                // 'uly' => currency['id'],
                // 'instId' => $market['id'],
                // 'ordType' => 'limit', // $market, $limit, post_only, fok, ioc, comma-separated stop orders => conditional, oco, $trigger, move_order_stop, iceberg, or twap
                // 'state' => 'canceled', // filled, canceled
                // 'after' => orderId,
                // 'before' => orderId,
                // 'limit' => $limit, // default 100, max 100
                // 'algoId' => "'433845797218942976'", // Algo order
            );
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['instId'] = $market['id'];
            }
            $type = null;
            $query = null;
            list($type, $query) = $this->handle_market_type_and_params('fetchCanceledOrders', $market, $params);
            $request['instType'] = $this->convert_to_instrument_type($type);
            if ($limit !== null) {
                $request['limit'] = $limit; // default 100, max 100
            }
            $request['state'] = 'canceled';
            $options = $this->safe_value($this->options, 'fetchCanceledOrders', array());
            $algoOrderTypes = $this->safe_value($this->options, 'algoOrderTypes', array());
            $defaultMethod = $this->safe_string($options, 'method', 'privateGetTradeOrdersHistory');
            $method = $this->safe_string($params, 'method', $defaultMethod);
            $ordType = $this->safe_string($params, 'ordType');
            $trigger = $this->safe_value_2($params, 'stop', 'trigger');
            $trailing = $this->safe_bool($params, 'trailing', false);
            if ($trailing) {
                $method = 'privateGetTradeOrdersAlgoHistory';
                $request['ordType'] = 'move_order_stop';
            } elseif ($trigger || (is_array($algoOrderTypes) && array_key_exists($ordType, $algoOrderTypes))) {
                $method = 'privateGetTradeOrdersAlgoHistory';
                $algoId = $this->safe_string($params, 'algoId');
                if ($algoId !== null) {
                    $request['algoId'] = $algoId;
                    $params = $this->omit($params, 'algoId');
                }
                if ($trigger) {
                    if ($ordType === null) {
                        throw new ArgumentsRequired($this->id . ' fetchCanceledOrders() requires an "ordType" string parameter, "conditional", "oco", "trigger", "move_order_stop", "iceberg", or "twap"');
                    }
                }
            } else {
                if ($since !== null) {
                    $request['begin'] = $since;
                }
                $until = $this->safe_integer($query, 'until');
                if ($until !== null) {
                    $request['end'] = $until;
                    $query = $this->omit($query, array( 'until' ));
                }
            }
            $send = $this->omit($query, array( 'method', 'stop', 'trigger', 'trailing' ));
            $response = null;
            if ($method === 'privateGetTradeOrdersAlgoHistory') {
                $response = Async\await($this->privateGetTradeOrdersAlgoHistory ($this->extend($request, $send)));
            } else {
                $response = Async\await($this->privateGetTradeOrdersHistory ($this->extend($request, $send)));
            }
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "accFillSz" => "0",
            //                 "avgPx" => "",
            //                 "cTime" => "1644037822494",
            //                 "category" => "normal",
            //                 "ccy" => "",
            //                 "clOrdId" => "",
            //                 "fee" => "0",
            //                 "feeCcy" => "BTC",
            //                 "fillPx" => "",
            //                 "fillSz" => "0",
            //                 "fillTime" => "",
            //                 "instId" => "BTC-USDT",
            //                 "instType" => "SPOT",
            //                 "lever" => "",
            //                 "ordId" => "410059580352409602",
            //                 "ordType" => "limit",
            //                 "pnl" => "0",
            //                 "posSide" => "net",
            //                 "px" => "30000",
            //                 "rebate" => "0",
            //                 "rebateCcy" => "USDT",
            //                 "side" => "buy",
            //                 "slOrdPx" => "",
            //                 "slTriggerPx" => "",
            //                 "slTriggerPxType" => "",
            //                 "source" => "",
            //                 "state" => "canceled",
            //                 "sz" => "0.0005452",
            //                 "tag" => "",
            //                 "tdMode" => "cash",
            //                 "tgtCcy" => "",
            //                 "tpOrdPx" => "",
            //                 "tpTriggerPx" => "",
            //                 "tpTriggerPxType" => "",
            //                 "tradeId" => "",
            //                 "uTime" => "1644038165667"
            //             }
            //         ),
            //         "msg" => ""
            //     }
            //
            // Algo order
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             array(
            //                 "activePx" => "",
            //                 "activePxType" => "",
            //                 "actualPx" => "",
            //                 "actualSide" => "buy",
            //                 "actualSz" => "0",
            //                 "algoId" => "433845797218942976",
            //                 "cTime" => "1649708898523",
            //                 "callbackRatio" => "",
            //                 "callbackSpread" => "",
            //                 "ccy" => "",
            //                 "ctVal" => "0.01",
            //                 "instId" => "BTC-USDT-SWAP",
            //                 "instType" => "SWAP",
            //                 "last" => "39950.4",
            //                 "lever" => "125",
            //                 "moveTriggerPx" => "",
            //                 "notionalUsd" => "1592.1760000000002",
            //                 "ordId" => "",
            //                 "ordPx" => "29000",
            //                 "ordType" => "trigger",
            //                 "posSide" => "long",
            //                 "pxLimit" => "",
            //                 "pxSpread" => "",
            //                 "pxVar" => "",
            //                 "side" => "buy",
            //                 "slOrdPx" => "",
            //                 "slTriggerPx" => "",
            //                 "slTriggerPxType" => "",
            //                 "state" => "canceled",
            //                 "sz" => "4",
            //                 "szLimit" => "",
            //                 "tag" => "",
            //                 "tdMode" => "isolated",
            //                 "tgtCcy" => "",
            //                 "timeInterval" => "",
            //                 "tpOrdPx" => "",
            //                 "tpTriggerPx" => "",
            //                 "tpTriggerPxType" => "",
            //                 "triggerPx" => "30000",
            //                 "triggerPxType" => "last",
            //                 "triggerTime" => "",
            //                 "uly" => "BTC-USDT"
            //             ),
            //         ),
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_orders($data, $market, $since, $limit);
        }) ();
    }

    public function fetch_closed_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetches information on multiple closed orders made by the user
             *
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-trade-get-order-history-last-7-days
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-algo-trading-get-algo-order-history
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-trade-get-order-history-last-3-months
             *
             * @param {string} $symbol unified $market $symbol of the $market orders were made in
             * @param {int} [$since] the earliest time in ms to fetch orders for
             * @param {int} [$limit] the maximum number of order structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {bool} [$params->trigger] True if fetching $trigger or conditional orders
             * @param {string} [$params->ordType] "conditional", "oco", "trigger", "move_order_stop", "iceberg", or "twap"
             * @param {string} [$params->algoId] Algo ID "'433845797218942976'"
             * @param {int} [$params->until] timestamp in ms to fetch orders for
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @param {string} [$params->method] $method to be used, either 'privateGetTradeOrdersHistory', 'privateGetTradeOrdersHistoryArchive' or 'privateGetTradeOrdersAlgoHistory' default is 'privateGetTradeOrdersHistory'
             * @param {boolean} [$params->trailing] set to true if you want to fetch $trailing orders
             * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchClosedOrders', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_dynamic('fetchClosedOrders', $symbol, $since, $limit, $params));
            }
            $request = array(
                // 'instType' => strtoupper($type), // SPOT, MARGIN, SWAP, FUTURES, OPTION
                // 'uly' => currency['id'],
                // 'instId' => $market['id'],
                // 'ordType' => 'limit', // $market, $limit, post_only, fok, ioc, comma-separated stop orders => conditional, oco, $trigger, move_order_stop, iceberg, or twap
                // 'state' => 'filled', // filled, effective
                // 'after' => orderId,
                // 'before' => orderId,
                // 'limit' => $limit, // default 100, max 100
                // 'algoId' => "'433845797218942976'", // Algo order
            );
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['instId'] = $market['id'];
            }
            $type = null;
            $query = null;
            list($type, $query) = $this->handle_market_type_and_params('fetchClosedOrders', $market, $params);
            $request['instType'] = $this->convert_to_instrument_type($type);
            if ($limit !== null) {
                $request['limit'] = $limit; // default 100, max 100
            }
            $options = $this->safe_dict($this->options, 'fetchClosedOrders', array());
            $algoOrderTypes = $this->safe_dict($this->options, 'algoOrderTypes', array());
            $defaultMethod = $this->safe_string($options, 'method', 'privateGetTradeOrdersHistory');
            $method = $this->safe_string($params, 'method', $defaultMethod);
            $ordType = $this->safe_string($params, 'ordType');
            $trigger = $this->safe_bool_2($params, 'stop', 'trigger');
            $trailing = $this->safe_bool($params, 'trailing', false);
            if ($trailing || $trigger || (is_array($algoOrderTypes) && array_key_exists($ordType, $algoOrderTypes))) {
                $method = 'privateGetTradeOrdersAlgoHistory';
                $request['state'] = 'effective';
            }
            if ($trailing) {
                $request['ordType'] = 'move_order_stop';
            } elseif ($trigger) {
                if ($ordType === null) {
                    $request['ordType'] = 'trigger';
                }
            } else {
                if ($since !== null) {
                    $request['begin'] = $since;
                }
                $until = $this->safe_integer($query, 'until');
                if ($until !== null) {
                    $request['end'] = $until;
                    $query = $this->omit($query, array( 'until' ));
                }
                $request['state'] = 'filled';
            }
            $send = $this->omit($query, array( 'method', 'stop', 'trigger', 'trailing' ));
            $response = null;
            if ($method === 'privateGetTradeOrdersAlgoHistory') {
                $response = Async\await($this->privateGetTradeOrdersAlgoHistory ($this->extend($request, $send)));
            } elseif ($method === 'privateGetTradeOrdersHistoryArchive') {
                $response = Async\await($this->privateGetTradeOrdersHistoryArchive ($this->extend($request, $send)));
            } else {
                $response = Async\await($this->privateGetTradeOrdersHistory ($this->extend($request, $send)));
            }
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "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" => ""
            //     }
            //
            // Algo order
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             array(
            //                 "activePx" => "",
            //                 "activePxType" => "",
            //                 "actualPx" => "",
            //                 "actualSide" => "buy",
            //                 "actualSz" => "0",
            //                 "algoId" => "433845797218942976",
            //                 "cTime" => "1649708898523",
            //                 "callbackRatio" => "",
            //                 "callbackSpread" => "",
            //                 "ccy" => "",
            //                 "ctVal" => "0.01",
            //                 "instId" => "BTC-USDT-SWAP",
            //                 "instType" => "SWAP",
            //                 "last" => "39950.4",
            //                 "lever" => "125",
            //                 "moveTriggerPx" => "",
            //                 "notionalUsd" => "1592.1760000000002",
            //                 "ordId" => "",
            //                 "ordPx" => "29000",
            //                 "ordType" => "trigger",
            //                 "posSide" => "long",
            //                 "pxLimit" => "",
            //                 "pxSpread" => "",
            //                 "pxVar" => "",
            //                 "side" => "buy",
            //                 "slOrdPx" => "",
            //                 "slTriggerPx" => "",
            //                 "slTriggerPxType" => "",
            //                 "state" => "effective",
            //                 "sz" => "4",
            //                 "szLimit" => "",
            //                 "tag" => "",
            //                 "tdMode" => "isolated",
            //                 "tgtCcy" => "",
            //                 "timeInterval" => "",
            //                 "tpOrdPx" => "",
            //                 "tpTriggerPx" => "",
            //                 "tpTriggerPxType" => "",
            //                 "triggerPx" => "30000",
            //                 "triggerPxType" => "last",
            //                 "triggerTime" => "",
            //                 "uly" => "BTC-USDT"
            //             ),
            //         ),
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_orders($data, $market, $since, $limit);
        }) ();
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetch all trades made by the user
             *
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-trade-get-transaction-details-last-3-months
             *
             * @param {string} $symbol unified $market $symbol
             * @param {int} [$since] the earliest time in ms to fetch trades for
             * @param {int} [$limit] the maximum number of trades structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] Timestamp in ms of the latest time to retrieve trades for
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchMyTrades', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_dynamic('fetchMyTrades', $symbol, $since, $limit, $params));
            }
            $request = array(
                // '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 = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['instId'] = $market['id'];
            }
            if ($since !== null) {
                $request['begin'] = $since;
            }
            list($request, $params) = $this->handle_until_option('end', $request, $params);
            list($type, $query) = $this->handle_market_type_and_params('fetchMyTrades', $market, $params);
            $request['instType'] = $this->convert_to_instrument_type($type);
            if (($limit !== null) && ($since === null)) {  // $limit = n, okx will return the n most recent results, instead of the n results after $limit, so $limit should only be sent when $since is null
                $request['limit'] = $limit; // default 100, max 100
            }
            $response = Async\await($this->privateGetTradeFillsHistory ($this->extend($request, $query)));
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "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 = $this->safe_list($response, 'data', array());
            return $this->parse_trades($data, $market, $since, $limit, $query);
        }) ();
    }

    public function fetch_order_trades(string $id, ?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $since, $limit, $params) {
            /**
             * fetch all the trades made from a single order
             *
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-trade-get-transaction-details-last-3-months
             *
             * @param {string} $id order $id
             * @param {string} $symbol unified market $symbol
             * @param {int} [$since] the earliest time in ms to fetch trades for
             * @param {int} [$limit] the maximum number of trades to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?$id=trade-structure trade structures~
             */
            $request = array(
                // '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 Async\await($this->fetch_my_trades($symbol, $since, $limit, $this->extend($request, $params)));
        }) ();
    }

    public function fetch_ledger(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch the history of changes, actions done by the user or operations that altered balance of the user
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-account-get-bills-details-last-7-days
             * @see https://www.okx.com/docs-v5/en/#rest-api-account-get-bills-details-last-3-months
             * @see https://www.okx.com/docs-v5/en/#rest-api-funding-asset-bills-details
             *
             * @param {string} [$code] unified $currency $code, default is null
             * @param {int} [$since] timestamp in ms of the earliest ledger entry, default is null
             * @param {int} [$limit] max number of ledger entries to return, default is null
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->marginMode] 'cross' or 'isolated'
             * @param {int} [$params->until] the latest time in ms to fetch entries for
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=ledger ledger structure~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchLedger', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_dynamic('fetchLedger', $code, $since, $limit, $params));
            }
            $options = $this->safe_dict($this->options, 'fetchLedger', array());
            $method = $this->safe_string($options, 'method');
            $method = $this->safe_string($params, 'method', $method);
            $params = $this->omit($params, 'method');
            $request = array(
                // 'instType' => null, // 'SPOT', 'MARGIN', 'SWAP', 'FUTURES", 'OPTION'
                // 'ccy' => null, // $currency['id'],
                // 'mgnMode' => null, // 'isolated', 'cross'
                // 'ctType' => null, // 'linear', 'inverse', only applicable to FUTURES/SWAP
                // 'type' => varies depending the 'method' endpoint :
                //     - https://www.okx.com/docs-v5/en/#rest-api-account-get-bills-details-last-7-days
                //     - https://www.okx.com/docs-v5/en/#rest-api-funding-asset-bills-details
                //     - https://www.okx.com/docs-v5/en/#rest-api-account-get-bills-details-last-3-months
                // 'after' => 'id', // return records earlier than the requested bill id
                // 'before' => 'id', // return records newer than the requested bill id
                // 'limit' => 100, // default 100, max 100
            );
            $marginMode = null;
            list($marginMode, $params) = $this->handle_margin_mode_and_params('fetchLedger', $params);
            if ($marginMode === null) {
                $marginMode = $this->safe_string($params, 'mgnMode');
            }
            if ($method !== 'privateGetAssetBills') {
                if ($marginMode !== null) {
                    $request['mgnMode'] = $marginMode;
                }
            }
            list($type, $query) = $this->handle_market_type_and_params('fetchLedger', null, $params);
            if ($type !== null) {
                $request['instType'] = $this->convert_to_instrument_type($type);
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $currency = null;
            if ($code !== null) {
                $currency = $this->currency($code);
                $request['ccy'] = $currency['id'];
            }
            list($request, $params) = $this->handle_until_option('end', $request, $params);
            $response = null;
            if ($method === 'privateGetAccountBillsArchive') {
                $response = Async\await($this->privateGetAccountBillsArchive ($this->extend($request, $query)));
            } elseif ($method === 'privateGetAssetBills') {
                $response = Async\await($this->privateGetAssetBills ($this->extend($request, $query)));
            } else {
                $response = Async\await($this->privateGetAccountBills ($this->extend($request, $query)));
            }
            //
            // privateGetAccountBills, privateGetAccountBillsArchive
            //
            //     {
            //         "code" => "0",
            //         "msg" => "",
            //         "data" => array(
            //             {
            //                 "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" => array(
            //             {
            //                 "billId" => "12344",
            //                 "ccy" => "BTC",
            //                 "balChg" => "2",
            //                 "bal" => "12",
            //                 "type" => "1",
            //                 "ts" => "1597026383085"
            //             }
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_ledger($data, $currency, $since, $limit);
        }) ();
    }

    public function parse_ledger_entry_type($type) {
        $types = array(
            '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 $this->safe_string($types, $type, $type);
    }

    public function parse_ledger_entry(array $item, ?array $currency = null): array {
        //
        // 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"
        //     }
        //
        $currencyId = $this->safe_string($item, 'ccy');
        $code = $this->safe_currency_code($currencyId, $currency);
        $currency = $this->safe_currency($currencyId, $currency);
        $timestamp = $this->safe_integer($item, 'ts');
        $feeCostString = $this->safe_string($item, 'fee');
        $fee = null;
        if ($feeCostString !== null) {
            $fee = array(
                'cost' => $this->parse_number(Precise::string_neg($feeCostString)),
                'currency' => $code,
            );
        }
        $marketId = $this->safe_string($item, 'instId');
        $symbol = $this->safe_symbol($marketId, null, '-');
        return $this->safe_ledger_entry(array(
            'info' => $item,
            'id' => $this->safe_string($item, 'billId'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'account' => null,
            'referenceId' => $this->safe_string($item, 'ordId'),
            'referenceAccount' => null,
            'type' => $this->parse_ledger_entry_type($this->safe_string($item, 'type')),
            'currency' => $code,
            'symbol' => $symbol,
            'amount' => $this->safe_number($item, 'balChg'),
            'before' => null,
            'after' => $this->safe_number($item, 'bal'),
            'status' => 'ok',
            'fee' => $fee,
        ), $currency);
    }

    public function parse_deposit_address($depositAddress, ?array $currency = null): array {
        //
        //     {
        //         "addr" => "okbtothemoon",
        //         "memo" => "971668", // may be missing
        //         "tag":"52055", // may be missing
        //         "pmtId" => "", // may be missing
        //         "ccy" => "BTC",
        //         "to" => "6", // 1 SPOT, 3 FUTURES, 6 FUNDING, 9 SWAP, 12 OPTION, 18 Unified account
        //         "selected" => true
        //     }
        //
        //     {
        //         "ccy":"usdt-erc20",
        //         "to":"6",
        //         "addr":"0x696abb81974a8793352cbd33aadcf78eda3cfdfa",
        //         "selected":true
        //     }
        //
        //     {
        //        "chain" => "ETH-OKExChain",
        //        "addrEx" => array( "comment" => "6040348" ), // some currencies like TON may have this field,
        //        "ctAddr" => "72315c",
        //        "ccy" => "ETH",
        //        "to" => "6",
        //        "addr" => "0x1c9f2244d1ccaa060bd536827c18925db10db102",
        //        "selected" => true
        //     }
        //
        $address = $this->safe_string($depositAddress, 'addr');
        $tag = $this->safe_string_n($depositAddress, array( 'tag', 'pmtId', 'memo' ));
        if ($tag === null) {
            $addrEx = $this->safe_value($depositAddress, 'addrEx', array());
            $tag = $this->safe_string($addrEx, 'comment');
        }
        $currencyId = $this->safe_string($depositAddress, 'ccy');
        $currency = $this->safe_currency($currencyId, $currency);
        $code = $currency['code'];
        $chain = $this->safe_string($depositAddress, 'chain');
        $networks = $this->safe_value($currency, 'networks', array());
        $networksById = $this->index_by($networks, 'id');
        $networkData = $this->safe_value($networksById, $chain);
        // inconsistent naming responses from exchange
        // with respect to $network naming provided in $currency info vs $address $chain-names and ids
        //
        // response from $address endpoint:
        //      {
        //          "chain" => "USDT-Polygon",
        //          "ctAddr" => "",
        //          "ccy" => "USDT",
        //          "to":"6" ,
        //          "addr" => "0x1903441e386cc49d937f6302955b5feb4286dcfa",
        //          "selected" => true
        //      }
        // $network information from $currency['networks'] field:
        // Polygon => {
        //        info => array(
        //            canDep => false,
        //            canInternal => false,
        //            canWd => false,
        //            ccy => 'USDT',
        //            $chain => 'USDT-Polygon-Bridge',
        //            mainNet => false,
        //            maxFee => '26.879528',
        //            minFee => '13.439764',
        //            minWd => '0.001',
        //            name => ''
        //        ),
        //        id => 'USDT-Polygon-Bridge',
        //        $network => 'Polygon',
        //        active => false,
        //        deposit => false,
        //        withdraw => false,
        //        fee => 13.439764,
        //        precision => null,
        //        limits => {
        //            withdraw => array(
        //                min => 0.001,
        //                max => null
        //            }
        //        }
        //     ),
        //
        if ($chain === 'USDT-Polygon') {
            $networkData = $this->safe_value_2($networksById, 'USDT-Polygon-Bridge', 'USDT-Polygon');
        }
        $network = $this->safe_string($networkData, 'network');
        $networkCode = $this->network_id_to_code($network, $code);
        $this->check_address($address);
        return array(
            'info' => $depositAddress,
            'currency' => $code,
            'network' => $networkCode,
            'address' => $address,
            'tag' => $tag,
        );
    }

    public function fetch_deposit_addresses_by_network(string $code, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $params) {
            /**
             * fetch a dictionary of addresses for a $currency, indexed by network
             *
             * @see https://www.okx.com/docs-v5/en/#funding-account-rest-api-get-deposit-address
             *
             * @param {string} $code unified $currency $code of the $currency for the deposit address
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=address-structure address structures~ indexed by the network
             */
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            $request = array(
                'ccy' => $currency['id'],
            );
            $response = Async\await($this->privateGetAssetDepositAddress ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "msg" => "",
            //         "data" => array(
            //             array(
            //                 "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
            //             ),
            //             // array("ccy":"usdt-erc20","to":"6","addr":"0x696abb81974a8793352cbd33aadcf78eda3cfdfa","selected":true),
            //             // array("ccy":"usdt-trc20","to":"6","addr":"TRrd5SiSZrfQVRKm4e9SRSbn2LNTYqCjqx","selected":true),
            //             // array("ccy":"usdt_okexchain","to":"6","addr":"0x696abb81974a8793352cbd33aadcf78eda3cfdfa","selected":true),
            //             // array("ccy":"usdt_kip20","to":"6","addr":"0x696abb81974a8793352cbd33aadcf78eda3cfdfa","selected":true),
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $filtered = $this->filter_by($data, 'selected', true);
            $parsed = $this->parse_deposit_addresses($filtered, [ $currency['code'] ], false);
            return $this->index_by($parsed, 'network');
        }) ();
    }

    public function fetch_deposit_address(string $code, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $params) {
            /**
             * fetch the deposit address for a currency associated with this account
             *
             * @see https://www.okx.com/docs-v5/en/#funding-account-rest-api-get-deposit-address
             *
             * @param {string} $code unified currency $code
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->network] the $network name for the deposit address
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=address-structure address structure~
             */
            Async\await($this->load_markets());
            $rawNetwork = $this->safe_string($params, 'network'); // some networks are like "Dora Vota Mainnet"
            $params = $this->omit($params, 'network');
            $code = $this->safe_currency_code($code);
            $network = $this->network_id_to_code($rawNetwork, $code);
            $response = Async\await($this->fetch_deposit_addresses_by_network($code, $params));
            if ($network !== null) {
                $result = $this->safe_dict($response, $network);
                if ($result === null) {
                    throw new InvalidAddress($this->id . ' fetchDepositAddress() cannot find ' . $network . ' deposit address for ' . $code);
                }
                return $result;
            }
            $codeNetwork = $this->network_id_to_code($code, $code);
            if (is_array($response) && array_key_exists($codeNetwork, $response)) {
                return $response[$codeNetwork];
            }
            // if the $network is not specified, return the $first address
            $keys = is_array($response) ? array_keys($response) : array();
            $first = $this->safe_string($keys, 0);
            return $this->safe_dict($response, $first);
        }) ();
    }

    public function withdraw(string $code, float $amount, string $address, ?string $tag = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $amount, $address, $tag, $params) {
            /**
             * make a withdrawal
             *
             * @see https://www.okx.com/docs-v5/en/#funding-account-rest-api-withdrawal
             *
             * @param {string} $code unified $currency $code
             * @param {float} $amount the $amount to withdraw
             * @param {string} $address the $address to withdraw to
             * @param {string} $tag
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=$transaction-structure $transaction structure~
             */
            list($tag, $params) = $this->handle_withdraw_tag_and_params($tag, $params);
            $this->check_address($address);
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            if (($tag !== null) && (strlen($tag) > 0)) {
                $address = $address . ':' . $tag;
            }
            $request = array(
                'ccy' => $currency['id'],
                'toAddr' => $address,
                'dest' => '4', // 2 = OKCoin International, 3 = OKX 4 = others
                'amt' => $this->number_to_string($amount),
            );
            $network = $this->safe_string($params, 'network'); // this line allows the user to specify either ERC20 or ETH
            if ($network !== null) {
                $networks = $this->safe_dict($this->options, 'networks', array());
                $network = $this->safe_string($networks, strtoupper($network), $network); // handle ETH>ERC20 alias
                $request['chain'] = $currency['id'] . '-' . $network;
                $params = $this->omit($params, 'network');
            }
            $fee = $this->safe_string($params, 'fee');
            if ($fee === null) {
                $currencies = Async\await($this->fetch_currencies());
                $this->currencies = $this->map_to_safe_map($this->deep_extend($this->currencies, $currencies));
                $targetNetwork = $this->safe_dict($currency['networks'], $this->network_id_to_code($network), array());
                $fee = $this->safe_string($targetNetwork, 'fee');
                if ($fee === null) {
                    throw new ArgumentsRequired($this->id . ' withdraw() requires a "fee" string parameter, $network $transaction $fee must be ≥ 0. Withdrawals to OKCoin or OKX are $fee-free, please set "0". Withdrawing to external digital asset $address requires $network $transaction $fee->');
                }
            }
            $request['fee'] = $this->number_to_string($fee); // withdrawals to OKCoin or OKX are $fee-free, please set 0
            $query = $this->omit($params, array( 'fee' ));
            $response = Async\await($this->privatePostAssetWithdrawal ($this->extend($request, $query)));
            //
            //     {
            //         "code" => "0",
            //         "msg" => "",
            //         "data" => array(
            //             {
            //                 "amt" => "0.1",
            //                 "wdId" => "67485",
            //                 "ccy" => "BTC"
            //             }
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $transaction = $this->safe_dict($data, 0);
            return $this->parse_transaction($transaction, $currency);
        }) ();
    }

    public function fetch_deposits(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch all deposits made to an account
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-funding-get-deposit-history
             *
             * @param {string} $code unified $currency $code
             * @param {int} [$since] the earliest time in ms to fetch deposits for
             * @param {int} [$limit] the maximum number of deposits structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] the latest time in ms to fetch entries for
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchDeposits', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_dynamic('fetchDeposits', $code, $since, $limit, $params));
            }
            $request = array(
                // 'ccy' => $currency['id'],
                // 'state' => 2, // 0 waiting for confirmation, 1 deposit credited, 2 deposit successful
                // 'after' => $since,
                // 'before' $this->milliseconds(),
                // 'limit' => $limit, // default 100, max 100
            );
            $currency = null;
            if ($code !== null) {
                $currency = $this->currency($code);
                $request['ccy'] = $currency['id'];
            }
            if ($since !== null) {
                $request['before'] = max ($since - 1, 0);
            }
            if ($limit !== null) {
                $request['limit'] = $limit; // default 100, max 100
            }
            list($request, $params) = $this->handle_until_option('after', $request, $params);
            $response = Async\await($this->privateGetAssetDepositHistory ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "msg" => "",
            //         "data" => array(
            //             array(
            //                 "amt" => "0.01044408",
            //                 "txId" => "1915737_3_0_0_asset",
            //                 "ccy" => "BTC",
            //                 "from" => "13801825426",
            //                 "to" => "",
            //                 "ts" => "1597026383085",
            //                 "state" => "2",
            //                 "depId" => "4703879"
            //             ),
            //             array(
            //                 "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 = $this->safe_list($response, 'data', array());
            return $this->parse_transactions($data, $currency, $since, $limit, $params);
        }) ();
    }

    public function fetch_deposit(string $id, ?string $code = null, $params = array ()) {
        return Async\async(function () use ($id, $code, $params) {
            /**
             * fetch $data on a $currency $deposit via the $deposit $id
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-funding-get-$deposit-history
             *
             * @param {string} $id $deposit $id
             * @param {string} $code filter by $currency $code
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?$id=transaction-structure transaction structure~
             */
            Async\await($this->load_markets());
            $request = array(
                'depId' => $id,
            );
            $currency = null;
            if ($code !== null) {
                $currency = $this->currency($code);
                $request['ccy'] = $currency['id'];
            }
            $response = Async\await($this->privateGetAssetDepositHistory ($this->extend($request, $params)));
            $data = $this->safe_value($response, 'data');
            $deposit = $this->safe_dict($data, 0, array());
            return $this->parse_transaction($deposit, $currency);
        }) ();
    }

    public function fetch_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch all withdrawals made from an account
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-funding-get-withdrawal-history
             *
             * @param {string} $code unified $currency $code
             * @param {int} [$since] the earliest time in ms to fetch withdrawals for
             * @param {int} [$limit] the maximum number of withdrawals structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] the latest time in ms to fetch entries for
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchWithdrawals', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_dynamic('fetchWithdrawals', $code, $since, $limit, $params));
            }
            $request = array(
                // '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' => $this->milliseconds(),
                // 'limit' => $limit, // default 100, max 100
            );
            $currency = null;
            if ($code !== null) {
                $currency = $this->currency($code);
                $request['ccy'] = $currency['id'];
            }
            if ($since !== null) {
                $request['before'] = max ($since - 1, 0);
            }
            if ($limit !== null) {
                $request['limit'] = $limit; // default 100, max 100
            }
            list($request, $params) = $this->handle_until_option('after', $request, $params);
            $response = Async\await($this->privateGetAssetWithdrawalHistory ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "msg" => "",
            //         "data" => array(
            //             array(
            //                 "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 = $this->safe_list($response, 'data', array());
            return $this->parse_transactions($data, $currency, $since, $limit, $params);
        }) ();
    }

    public function fetch_withdrawal(string $id, ?string $code = null, $params = array ()) {
        return Async\async(function () use ($id, $code, $params) {
            /**
             * fetch $data on a $currency $withdrawal via the $withdrawal $id
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-funding-get-$withdrawal-history
             *
             * @param {string} $id $withdrawal $id
             * @param {string} $code unified $currency $code of the $currency withdrawn, default is null
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?$id=transaction-structure transaction structure~
             */
            Async\await($this->load_markets());
            $request = array(
                'wdId' => $id,
            );
            $currency = null;
            if ($code !== null) {
                $currency = $this->currency($code);
                $request['ccy'] = $currency['id'];
            }
            $response = Async\await($this->privateGetAssetWithdrawalHistory ($this->extend($request, $params)));
            //
            //    {
            //        "code" => "0",
            //        "data" => array(
            //            {
            //                "chain" => "USDT-TRC20",
            //                "clientId" => '',
            //                "fee" => "0.8",
            //                "ccy" => "USDT",
            //                "amt" => "54.561",
            //                "txId" => "00cff6ec7fa7c7d7d184bd84e82b9ff36863f07c0421188607f87dfa94e06b70",
            //                "from" => "example@email.com",
            //                "to" => "TEY6qjnKDyyq5jDc3DJizWLCdUySrpQ4yp",
            //                "state" => "2",
            //                "ts" => "1641376485000",
            //                "wdId" => "25147041"
            //            }
            //        ),
            //        "msg" => ''
            //    }
            //
            $data = $this->safe_list($response, 'data', array());
            $withdrawal = $this->safe_dict($data, 0, array());
            return $this->parse_transaction($withdrawal);
        }) ();
    }

    public function parse_transaction_status(?string $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 = array(
            '-3' => 'pending',
            '-2' => 'canceled',
            '-1' => 'failed',
            '0' => 'pending',
            '1' => 'pending',
            '2' => 'ok',
            '3' => 'pending',
            '4' => 'pending',
            '5' => 'pending',
            '6' => 'pending',
            '7' => 'pending',
            '8' => 'pending',
            '9' => 'pending',
            '10' => 'pending',
            '12' => 'pending',
            '15' => 'pending',
            '16' => 'pending',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_transaction(array $transaction, ?array $currency = null): array {
        //
        // withdraw
        //
        //     {
        //         "amt" => "0.1",
        //         "wdId" => "67485",
        //         "ccy" => "BTC"
        //     }
        //
        // fetchWithdrawals
        //
        //     {
        //         "amt" => "0.094",
        //         "wdId" => "4703879",
        //         "fee" => "0.01000000eth",
        //         "txId" => "0x62477bac6509a04512819bb1455e923a60dea5966c7caeaa0b24eb8fb0432b85",
        //         "ccy" => "ETH",
        //         "from" => "13426335357",
        //         "to" => "0xA41446125D0B5b6785f6898c9D67874D763A1519",
        //         "tag",
        //         "pmtId",
        //         "memo",
        //         "ts" => "1597026383085",
        //         "state" => "2"
        //     }
        //
        // fetchDeposits
        //
        //     {
        //         "amt" => "0.01044408",
        //         "txId" => "1915737_3_0_0_asset",
        //         "ccy" => "BTC",
        //         "from" => "13801825426",
        //         "to" => "",
        //         "ts" => "1597026383085",
        //         "state" => "2",
        //         "depId" => "4703879"
        //     }
        //
        $type = null;
        $id = null;
        $withdrawalId = $this->safe_string($transaction, 'wdId');
        $addressFrom = $this->safe_string($transaction, 'from');
        $addressTo = $this->safe_string($transaction, 'to');
        $address = $addressTo;
        $tagTo = $this->safe_string_2($transaction, 'tag', 'memo');
        $tagTo = $this->safe_string_2($transaction, 'pmtId', $tagTo);
        if ($withdrawalId !== null) {
            $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 = $this->safe_string($transaction, 'depId');
            $type = 'deposit';
        }
        $currencyId = $this->safe_string($transaction, 'ccy');
        $code = $this->safe_currency_code($currencyId);
        $amount = $this->safe_number($transaction, 'amt');
        $status = $this->parse_transaction_status($this->safe_string($transaction, 'state'));
        $txid = $this->safe_string($transaction, 'txId');
        $timestamp = $this->safe_integer($transaction, 'ts');
        $feeCost = null;
        if ($type === 'deposit') {
            $feeCost = 0;
        } else {
            $feeCost = $this->safe_number($transaction, 'fee');
        }
        // todo parse tags
        return array(
            'info' => $transaction,
            'id' => $id,
            'currency' => $code,
            'amount' => $amount,
            'network' => null,
            'addressFrom' => $addressFrom,
            'addressTo' => $addressTo,
            'address' => $address,
            'tagFrom' => null,
            'tagTo' => $tagTo,
            'tag' => $tagTo,
            'status' => $status,
            'type' => $type,
            'updated' => null,
            'txid' => $txid,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'internal' => null,
            'comment' => null,
            'fee' => array(
                'currency' => $code,
                'cost' => $feeCost,
            ),
        );
    }

    public function fetch_leverage(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetch the set leverage for a $market
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-account-get-leverage
             *
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->marginMode] 'cross' or 'isolated'
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=leverage-structure leverage structure~
             */
            Async\await($this->load_markets());
            $marginMode = null;
            list($marginMode, $params) = $this->handle_margin_mode_and_params('fetchLeverage', $params);
            if ($marginMode === null) {
                $marginMode = $this->safe_string($params, 'mgnMode', 'cross'); // cross $marginMode
            }
            if (($marginMode !== 'cross') && ($marginMode !== 'isolated')) {
                throw new BadRequest($this->id . ' fetchLeverage() requires a $marginMode parameter that must be either cross or isolated');
            }
            $market = $this->market($symbol);
            $request = array(
                'instId' => $market['id'],
                'mgnMode' => $marginMode,
            );
            $response = Async\await($this->privateGetAccountLeverageInfo ($this->extend($request, $params)));
            //
            //     {
            //        "code" => "0",
            //        "data" => array(
            //            {
            //                "instId" => "BTC-USDT-SWAP",
            //                "lever" => "5.00000000",
            //                "mgnMode" => "isolated",
            //                "posSide" => "net"
            //            }
            //        ),
            //        "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_leverage($data, $market);
        }) ();
    }

    public function parse_leverage(array $leverage, ?array $market = null): array {
        $marketId = null;
        $marginMode = null;
        $longLeverage = null;
        $shortLeverage = null;
        for ($i = 0; $i < count($leverage); $i++) {
            $entry = $leverage[$i];
            $marginMode = $this->safe_string_lower($entry, 'mgnMode');
            $marketId = $this->safe_string($entry, 'instId');
            $positionSide = $this->safe_string_lower($entry, 'posSide');
            if ($positionSide === 'long') {
                $longLeverage = $this->safe_integer($entry, 'lever');
            } elseif ($positionSide === 'short') {
                $shortLeverage = $this->safe_integer($entry, 'lever');
            } else {
                $longLeverage = $this->safe_integer($entry, 'lever');
                $shortLeverage = $this->safe_integer($entry, 'lever');
            }
        }
        return array(
            'info' => $leverage,
            'symbol' => $this->safe_symbol($marketId, $market),
            'marginMode' => $marginMode,
            'longLeverage' => $longLeverage,
            'shortLeverage' => $shortLeverage,
        );
    }

    public function fetch_position(string $symbol, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetch $data on a single open contract trade $position
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-account-get-positions
             *
             * @param {string} $symbol unified $market $symbol of the $market the $position is held in, default is null
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->instType] MARGIN, SWAP, FUTURES, OPTION
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=$position-structure $position structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            list($type, $query) = $this->handle_market_type_and_params('fetchPosition', $market, $params);
            $request = array(
                // instType 'strval' No Instrument $type, MARGIN, SWAP, FUTURES, OPTION
                'instId' => $market['id'],
                // posId 'strval' No Single $position ID or multiple $position IDs (no more than 20) separated with comma
            );
            if ($type !== null) {
                $request['instType'] = $this->convert_to_instrument_type($type);
            }
            $response = Async\await($this->privateGetAccountPositions ($this->extend($request, $query)));
            //
            //     {
            //         "code" => "0",
            //         "msg" => "",
            //         "data" => array(
            //             {
            //                 "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 = $this->safe_list($response, 'data', array());
            $position = $this->safe_dict($data, 0);
            if ($position === null) {
                return null;
            }
            return $this->parse_position($position, $market);
        }) ();
    }

    public function fetch_positions(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-account-get-$positions
             * @see https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-$positions-history history
             *
             * fetch all open $positions
             * @param {string[]|null} $symbols list of unified $market $symbols
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->instType] MARGIN, SWAP, FUTURES, OPTION
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=position-structure position structure~
             */
            Async\await($this->load_markets());
            $request = array(
                // 'instType' => 'MARGIN', // optional string, MARGIN, SWAP, FUTURES, OPTION
                // 'instId' => $market['id'], // optional string, e.g. 'BTC-USD-190927-5000-C'
                // 'posId' => '307173036051017730', // optional string, Single or multiple position IDs (no more than 20) separated with commas
            );
            if ($symbols !== null) {
                $marketIds = array();
                for ($i = 0; $i < count($symbols); $i++) {
                    $entry = $symbols[$i];
                    $market = $this->market($entry);
                    $marketIds[] = $market['id'];
                }
                $marketIdsLength = count($marketIds);
                if ($marketIdsLength > 0) {
                    $request['instId'] = implode(',', $marketIds);
                }
            }
            $fetchPositionsOptions = $this->safe_dict($this->options, 'fetchPositions', array());
            $method = $this->safe_string($fetchPositionsOptions, 'method', 'privateGetAccountPositions');
            $response = null;
            if ($method === 'privateGetAccountPositionsHistory') {
                $response = Async\await($this->privateGetAccountPositionsHistory ($this->extend($request, $params)));
            } else {
                $response = Async\await($this->privateGetAccountPositions ($this->extend($request, $params)));
            }
            //
            //     {
            //         "code" => "0",
            //         "msg" => "",
            //         "data" => array(
            //             {
            //                 "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 = $this->safe_list($response, 'data', array());
            $result = array();
            for ($i = 0; $i < count($positions); $i++) {
                $result[] = $this->parse_position($positions[$i]);
            }
            return $this->filter_by_array_positions($result, 'symbol', $this->market_symbols($symbols), false);
        }) ();
    }

    public function fetch_positions_for_symbol(string $symbol, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-account-get-positions
             *
             * fetch all open positions for specific $symbol
             * @param {string} $symbol unified market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->instType] MARGIN (if needed)
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=position-structure position structure~
             */
            return Async\await($this->fetch_positions(array( $symbol ), $params));
        }) ();
    }

    public function parse_position(array $position, ?array $market = null) {
        //
        //     {
        //        "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" => ""
        //    }
        // history
        //    {
        //        "cTime":"1708351230102",
        //        "ccy":"USDT",
        //        "closeAvgPx":"1.2567",
        //        "closeTotalPos":"40",
        //        "direction":"short",
        //        "fee":"-0.0351036",
        //        "fundingFee":"0",
        //        "instId":"SUSHI-USDT-SWAP",
        //        "instType":"SWAP",
        //        "lever":"10.0",
        //        "liqPenalty":"0",
        //        "mgnMode":"isolated",
        //        "openAvgPx":"1.2462",
        //        "openMaxPos":"40",
        //        "pnl":"-0.42",
        //        "pnlRatio":"-0.0912982667308618",
        //        "posId":"666159086676836352",
        //        "realizedPnl":"-0.4551036",
        //        "triggerPx":"",
        //        "type":"2",
        //        "uTime":"1708354805699",
        //        "uly":"SUSHI-USDT"
        //    }
        //
        $marketId = $this->safe_string($position, 'instId');
        $market = $this->safe_market($marketId, $market, null, 'contract');
        $symbol = $market['symbol'];
        $pos = $this->safe_string($position, 'pos'); // 'pos' field => One way mode => 0 if $position is not open, 1 if open | Two way (hedge) mode => -1 if short, 1 if long, 0 if $position is not open
        $contractsAbs = Precise::string_abs($pos);
        $side = $this->safe_string_2($position, 'posSide', 'direction');
        $hedged = $side !== 'net';
        $contracts = $this->parse_number($contractsAbs);
        if ($market['margin']) {
            // margin $position
            if ($side === 'net') {
                $posCcy = $this->safe_string($position, 'posCcy');
                $parsedCurrency = $this->safe_currency_code($posCcy);
                if ($parsedCurrency !== null) {
                    $side = ($market['base'] === $parsedCurrency) ? 'long' : 'short';
                }
            }
            if ($side === null) {
                $side = $this->safe_string($position, 'direction');
            }
        } else {
            if ($pos !== null) {
                if ($side === 'net') {
                    if (Precise::string_gt($pos, '0')) {
                        $side = 'long';
                    } elseif (Precise::string_lt($pos, '0')) {
                        $side = 'short';
                    } else {
                        $side = null;
                    }
                }
            }
        }
        $contractSize = $this->safe_number($market, 'contractSize');
        $contractSizeString = $this->number_to_string($contractSize);
        $markPriceString = $this->safe_string($position, 'markPx');
        $notionalString = $this->safe_string($position, 'notionalUsd');
        if ($market['inverse']) {
            $notionalString = Precise::string_div(Precise::string_mul($contractsAbs, $contractSizeString), $markPriceString);
        }
        $notional = $this->parse_number($notionalString);
        $marginMode = $this->safe_string($position, 'mgnMode');
        $initialMarginString = null;
        $entryPriceString = $this->safe_string_2($position, 'avgPx', 'openAvgPx');
        $unrealizedPnlString = $this->safe_string($position, 'upl');
        $leverageString = $this->safe_string($position, 'lever');
        $initialMarginPercentage = null;
        $collateralString = null;
        if ($marginMode === 'cross') {
            $initialMarginString = $this->safe_string($position, 'imr');
            $collateralString = Precise::string_add($initialMarginString, $unrealizedPnlString);
        } elseif ($marginMode === 'isolated') {
            $initialMarginPercentage = Precise::string_div('1', $leverageString);
            $collateralString = $this->safe_string($position, 'margin');
        }
        $maintenanceMarginString = $this->safe_string($position, 'mmr');
        $maintenanceMargin = $this->parse_number($maintenanceMarginString);
        $maintenanceMarginPercentageString = Precise::string_div($maintenanceMarginString, $notionalString);
        if ($initialMarginPercentage === null) {
            $initialMarginPercentage = $this->parse_number(Precise::string_div($initialMarginString, $notionalString, 4));
        } elseif ($initialMarginString === null) {
            $initialMarginString = Precise::string_mul($initialMarginPercentage, $notionalString);
        }
        $rounder = '0.00005'; // round to closest 0.01%
        $maintenanceMarginPercentage = $this->parse_number(Precise::string_div(Precise::string_add($maintenanceMarginPercentageString, $rounder), '1', 4));
        $liquidationPrice = $this->safe_number($position, 'liqPx');
        $percentageString = $this->safe_string($position, 'uplRatio');
        $percentage = $this->parse_number(Precise::string_mul($percentageString, '100'));
        $timestamp = $this->safe_integer($position, 'cTime');
        $marginRatio = $this->parse_number(Precise::string_div($maintenanceMarginString, $collateralString, 4));
        return $this->safe_position(array(
            'info' => $position,
            'id' => $this->safe_string($position, 'posId'),
            'symbol' => $symbol,
            'notional' => $notional,
            'marginMode' => $marginMode,
            'liquidationPrice' => $liquidationPrice,
            'entryPrice' => $this->parse_number($entryPriceString),
            'unrealizedPnl' => $this->parse_number($unrealizedPnlString),
            'realizedPnl' => $this->safe_number($position, 'realizedPnl'),
            'percentage' => $percentage,
            'contracts' => $contracts,
            'contractSize' => $contractSize,
            'markPrice' => $this->parse_number($markPriceString),
            'lastPrice' => $this->safe_number($position, 'closeAvgPx'),
            'side' => $side,
            'hedged' => $hedged,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastUpdateTimestamp' => $this->safe_integer($position, 'uTime'),
            'maintenanceMargin' => $maintenanceMargin,
            'maintenanceMarginPercentage' => $maintenanceMarginPercentage,
            'collateral' => $this->parse_number($collateralString),
            'initialMargin' => $this->parse_number($initialMarginString),
            'initialMarginPercentage' => $this->parse_number($initialMarginPercentage),
            'leverage' => $this->parse_number($leverageString),
            'marginRatio' => $marginRatio,
            'stopLossPrice' => null,
            'takeProfitPrice' => null,
        ));
    }

    public function transfer(string $code, float $amount, string $fromAccount, string $toAccount, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $amount, $fromAccount, $toAccount, $params) {
            /**
             * transfer $currency internally between wallets on the same account
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-funding-funds-transfer
             *
             * @param {string} $code unified $currency $code
             * @param {float} $amount amount to transfer
             * @param {string} $fromAccount account to transfer from
             * @param {string} $toAccount account to transfer to
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=transfer-structure transfer structure~
             */
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            $accountsByType = $this->safe_dict($this->options, 'accountsByType', array());
            $fromId = $this->safe_string($accountsByType, $fromAccount, $fromAccount);
            $toId = $this->safe_string($accountsByType, $toAccount, $toAccount);
            $request = array(
                'ccy' => $currency['id'],
                'amt' => $this->currency_to_precision($code, $amount),
                'type' => '0', // 0 = transfer within account by default, 1 = master account to sub-account, 2 = sub-account to master account, 3 = sub-account to master account (Only applicable to APIKey from sub-account), 4 = sub-account to sub-account
                'from' => $fromId, // remitting account, 6 => Funding account, 18 => Trading account
                'to' => $toId, // beneficiary account, 6 => Funding account, 18 => Trading account
                // 'subAcct' => 'sub-account-name', // optional, only required when type is 1, 2 or 4
                // 'loanTrans' => false, // Whether or not borrowed coins can be transferred out under Multi-$currency margin and Portfolio margin. The default is false
                // 'clientId' => 'client-supplied id', // A combination of case-sensitive alphanumerics, all numbers, or all letters of up to 32 characters
                // 'omitPosRisk' => false, // Ignore position risk. Default is false. Applicable to Portfolio margin
            );
            if ($fromId === 'master') {
                $request['type'] = '1';
                $request['subAcct'] = $toId;
                $request['from'] = $this->safe_string($params, 'from', '6');
                $request['to'] = $this->safe_string($params, 'to', '6');
            } elseif ($toId === 'master') {
                $request['type'] = '2';
                $request['subAcct'] = $fromId;
                $request['from'] = $this->safe_string($params, 'from', '6');
                $request['to'] = $this->safe_string($params, 'to', '6');
            }
            $response = Async\await($this->privatePostAssetTransfer ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "msg" => "",
            //         "data" => array(
            //             {
            //                 "transId" => "754147",
            //                 "ccy" => "USDT",
            //                 "from" => "6",
            //                 "amt" => "0.1",
            //                 "to" => "18"
            //             }
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $rawTransfer = $this->safe_dict($data, 0, array());
            return $this->parse_transfer($rawTransfer, $currency);
        }) ();
    }

    public function parse_transfer(array $transfer, ?array $currency = null): array {
        //
        // $transfer
        //
        //     {
        //         "transId" => "754147",
        //         "ccy" => "USDT",
        //         "from" => "6",
        //         "amt" => "0.1",
        //         "to" => "18"
        //     }
        //
        // fetchTransfer
        //
        //     {
        //         "amt" => "5",
        //         "ccy" => "USDT",
        //         "from" => "18",
        //         "instId" => "",
        //         "state" => "success",
        //         "subAcct" => "",
        //         "to" => "6",
        //         "toInstId" => "",
        //         "transId" => "464424732",
        //         "type" => "0"
        //     }
        //
        // fetchTransfers
        //
        //     {
        //         "bal" => "70.6874353780312913",
        //         "balChg" => "-4.0000000000000000", // negative means "to funding", positive meand "from funding"
        //         "billId" => "588900695232225299",
        //         "ccy" => "USDT",
        //         "execType" => "",
        //         "fee" => "",
        //         "from" => "18",
        //         "instId" => "",
        //         "instType" => "",
        //         "mgnMode" => "",
        //         "notes" => "To Funding Account",
        //         "ordId" => "",
        //         "pnl" => "",
        //         "posBal" => "",
        //         "posBalChg" => "",
        //         "price" => "0",
        //         "subType" => "12",
        //         "sz" => "-4",
        //         "to" => "6",
        //         "ts" => "1686676866989",
        //         "type" => "1"
        //     }
        //
        $id = $this->safe_string_2($transfer, 'transId', 'billId');
        $currencyId = $this->safe_string($transfer, 'ccy');
        $code = $this->safe_currency_code($currencyId, $currency);
        $amount = $this->safe_number($transfer, 'amt');
        $fromAccountId = $this->safe_string($transfer, 'from');
        $toAccountId = $this->safe_string($transfer, 'to');
        $accountsById = $this->safe_dict($this->options, 'accountsById', array());
        $timestamp = $this->safe_integer($transfer, 'ts');
        $balanceChange = $this->safe_string($transfer, 'sz');
        if ($balanceChange !== null) {
            $amount = $this->parse_number(Precise::string_abs($balanceChange));
        }
        return array(
            'info' => $transfer,
            'id' => $id,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'currency' => $code,
            'amount' => $amount,
            'fromAccount' => $this->safe_string($accountsById, $fromAccountId),
            'toAccount' => $this->safe_string($accountsById, $toAccountId),
            'status' => $this->parse_transfer_status($this->safe_string($transfer, 'state')),
        );
    }

    public function parse_transfer_status(?string $status): ?string {
        $statuses = array(
            'success' => 'ok',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function fetch_transfer(string $id, ?string $code = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($id, $code, $params) {
            Async\await($this->load_markets());
            $request = array(
                'transId' => $id,
                // 'type' => 0, // default is 0 $transfer within account, 1 master to sub, 2 sub to master
            );
            $response = Async\await($this->privateGetAssetTransferState ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "amt" => "5",
            //                 "ccy" => "USDT",
            //                 "from" => "18",
            //                 "instId" => "",
            //                 "state" => "success",
            //                 "subAcct" => "",
            //                 "to" => "6",
            //                 "toInstId" => "",
            //                 "transId" => "464424732",
            //                 "type" => "0"
            //             }
            //         ),
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $transfer = $this->safe_dict($data, 0);
            return $this->parse_transfer($transfer);
        }) ();
    }

    public function fetch_transfers(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch a history of internal $transfers made on an account
             *
             * @see https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-bills-details-last-3-months
             *
             * @param {string} $code unified $currency $code of the $currency transferred
             * @param {int} [$since] the earliest time in ms to fetch $transfers for
             * @param {int} [$limit] the maximum number of $transfers structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transfer-structure transfer structures~
             */
            Async\await($this->load_markets());
            $currency = null;
            $request = array(
                'type' => '1', // https://www.okx.com/docs-v5/en/#rest-api-account-get-bills-details-last-3-months
            );
            if ($code !== null) {
                $currency = $this->currency($code);
                $request['ccy'] = $currency['id'];
            }
            if ($since !== null) {
                $request['begin'] = $since;
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $response = Async\await($this->privateGetAccountBillsArchive ($this->extend($request, $params)));
            //
            //    {
            //        "code" => "0",
            //        "data" => array(
            //            array(
            //                "bal" => "70.6874353780312913",
            //                "balChg" => "-4.0000000000000000",
            //                "billId" => "588900695232225299",
            //                "ccy" => "USDT",
            //                "execType" => "",
            //                "fee" => "",
            //                "from" => "18",
            //                "instId" => "",
            //                "instType" => "",
            //                "mgnMode" => "",
            //                "notes" => "To Funding Account",
            //                "ordId" => "",
            //                "pnl" => "",
            //                "posBal" => "",
            //                "posBalChg" => "",
            //                "price" => "0",
            //                "subType" => "12",
            //                "sz" => "-4",
            //                "to" => "6",
            //                "ts" => "1686676866989",
            //                "type" => "1"
            //            ),
            //            ...
            //        ),
            //        "msg" => ""
            //    }
            //
            $transfers = $this->safe_list($response, 'data', array());
            return $this->parse_transfers($transfers, $currency, $since, $limit, $params);
        }) ();
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $isArray = gettype($params) === 'array' && array_keys($params) === array_keys(array_keys($params));
        $request = '/api/' . $this->version . '/' . $this->implode_params($path, $params);
        $query = $this->omit($params, $this->extract_params($path));
        $url = $this->implode_hostname($this->urls['api']['rest']) . $request;
        // $type = $this->getPathAuthenticationType ($path);
        if ($api === 'public') {
            if ($query) {
                $url .= '?' . $this->urlencode($query);
            }
        } elseif ($api === 'private') {
            $this->check_required_credentials();
            // inject id in implicit $api call
            if ($method === 'POST' && ($path === 'trade/batch-orders' || $path === 'trade/order-algo' || $path === 'trade/order')) {
                $brokerId = $this->safe_string($this->options, 'brokerId', '6b9ad766b55dBCDE');
                if (gettype($params) === 'array' && array_keys($params) === array_keys(array_keys($params))) {
                    for ($i = 0; $i < count($params); $i++) {
                        $entry = $params[$i];
                        $clientOrderId = $this->safe_string($entry, 'clOrdId');
                        if ($clientOrderId === null) {
                            $entry['clOrdId'] = $brokerId . $this->uuid16();
                            $entry['tag'] = $brokerId;
                            $params[$i] = $entry;
                        }
                    }
                } else {
                    $clientOrderId = $this->safe_string($params, 'clOrdId');
                    if ($clientOrderId === null) {
                        $params['clOrdId'] = $brokerId . $this->uuid16();
                        $params['tag'] = $brokerId;
                    }
                }
            }
            $timestamp = $this->iso8601($this->nonce());
            $headers = array(
                'OK-ACCESS-KEY' => $this->apiKey,
                'OK-ACCESS-PASSPHRASE' => $this->password,
                'OK-ACCESS-TIMESTAMP' => $timestamp,
                // 'OK-FROM' => '',
                // 'OK-TO' => '',
                // 'OK-LIMIT' => '',
            );
            $auth = $timestamp . $method . $request;
            if ($method === 'GET') {
                if ($query) {
                    $urlencodedQuery = '?' . $this->urlencode($query);
                    $url .= $urlencodedQuery;
                    $auth .= $urlencodedQuery;
                }
            } else {
                if ($isArray || $query) {
                    $body = $this->json($query);
                    $auth .= $body;
                }
                $headers['Content-Type'] = 'application/json';
            }
            $signature = $this->hmac($this->encode($auth), $this->encode($this->secret), 'sha256', 'base64');
            $headers['OK-ACCESS-SIGN'] = $signature;
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function parse_funding_rate($contract, ?array $market = null): array {
        //
        //    {
        //        "fundingRate" => "0.00027815",
        //        "fundingTime" => "1634256000000",
        //        "instId" => "BTC-USD-SWAP",
        //        "instType" => "SWAP",
        //        "nextFundingRate" => "0.00017",
        //        "nextFundingTime" => "1634284800000"
        //    }
        // ws
        //     {
        //        "fundingRate":"0.0001875391284828",
        //        "fundingTime":"1700726400000",
        //        "instId":"BTC-USD-SWAP",
        //        "instType":"SWAP",
        //        "method" => "next_period",
        //        "maxFundingRate":"0.00375",
        //        "minFundingRate":"-0.00375",
        //        "nextFundingRate":"0.0002608059239328",
        //        "nextFundingTime":"1700755200000",
        //        "premium" => "0.0001233824646391",
        //        "settFundingRate":"0.0001699799259033",
        //        "settState":"settled",
        //        "ts":"1700724675402"
        //     }
        //
        // in the response above $nextFundingRate is actually two funding rates from now
        //
        $nextFundingRateTimestamp = $this->safe_integer($contract, 'nextFundingTime');
        $marketId = $this->safe_string($contract, 'instId');
        $symbol = $this->safe_symbol($marketId, $market);
        $nextFundingRate = $this->safe_number($contract, 'nextFundingRate');
        $fundingTime = $this->safe_integer($contract, 'fundingTime');
        $fundingTimeString = $this->safe_string($contract, 'fundingTime');
        $nextFundingTimeString = $this->safe_string($contract, 'nextFundingTime');
        $millisecondsInterval = Precise::string_sub($nextFundingTimeString, $fundingTimeString);
        // https://www.okx.com/support/hc/en-us/articles/360053909272-Ⅸ-Introduction-to-perpetual-swap-funding-fee
        // > The current interest is 0.
        return array(
            'info' => $contract,
            'symbol' => $symbol,
            'markPrice' => null,
            'indexPrice' => null,
            'interestRate' => $this->parse_number('0'),
            'estimatedSettlePrice' => null,
            'timestamp' => null,
            'datetime' => null,
            'fundingRate' => $this->safe_number($contract, 'fundingRate'),
            'fundingTimestamp' => $fundingTime,
            'fundingDatetime' => $this->iso8601($fundingTime),
            'nextFundingRate' => $nextFundingRate,
            'nextFundingTimestamp' => $nextFundingRateTimestamp,
            'nextFundingDatetime' => $this->iso8601($nextFundingRateTimestamp),
            'previousFundingRate' => null,
            'previousFundingTimestamp' => null,
            'previousFundingDatetime' => null,
            'interval' => $this->parse_funding_interval($millisecondsInterval),
        );
    }

    public function parse_funding_interval($interval) {
        $intervals = array(
            '3600000' => '1h',
            '14400000' => '4h',
            '28800000' => '8h',
            '57600000' => '16h',
            '86400000' => '24h',
        );
        return $this->safe_string($intervals, $interval, $interval);
    }

    public function fetch_funding_interval(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetch the current funding rate interval
             *
             * @see https://www.okx.com/docs-v5/en/#public-data-rest-api-get-funding-rate
             *
             * @param {string} $symbol unified market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=funding-rate-structure funding rate structure~
             */
            return Async\await($this->fetch_funding_rate($symbol, $params));
        }) ();
    }

    public function fetch_funding_rate(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetch the current funding rate
             *
             * @see https://www.okx.com/docs-v5/en/#public-$data-rest-api-get-funding-rate
             *
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=funding-rate-structure funding rate structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            if (!$market['swap']) {
                throw new ExchangeError($this->id . ' fetchFundingRate() is only valid for swap markets');
            }
            $request = array(
                'instId' => $market['id'],
            );
            $response = Async\await($this->publicGetPublicFundingRate ($this->extend($request, $params)));
            //
            //    {
            //        "code" => "0",
            //        "data" => array(
            //            {
            //                "fundingRate" => "0.00027815",
            //                "fundingTime" => "1634256000000",
            //                "instId" => "BTC-USD-SWAP",
            //                "instType" => "SWAP",
            //                "nextFundingRate" => "0.00017",
            //                "nextFundingTime" => "1634284800000"
            //            }
            //        ),
            //        "msg" => ""
            //    }
            //
            $data = $this->safe_list($response, 'data', array());
            $entry = $this->safe_dict($data, 0, array());
            return $this->parse_funding_rate($entry, $market);
        }) ();
    }

    public function fetch_funding_rates(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetches the current funding rates for multiple $symbols
             *
             * @see https://www.okx.com/docs-v5/en/#public-$data-rest-api-get-funding-rate
             *
             * @param {string[]} $symbols unified market $symbols
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=funding-rates-structure funding rates structure~
             */
            Async\await($this->load_markets());
            $symbols = $this->market_symbols($symbols, 'swap', true);
            $request = array( 'instId' => 'ANY' );
            $response = Async\await($this->publicGetPublicFundingRate ($this->extend($request, $params)));
            //
            //    {
            //        "code" => "0",
            //        "data" => array(
            //            {
            //                "fundingRate" => "0.00027815",
            //                "fundingTime" => "1634256000000",
            //                "instId" => "BTC-USD-SWAP",
            //                "instType" => "SWAP",
            //                "nextFundingRate" => "0.00017",
            //                "nextFundingTime" => "1634284800000"
            //            }
            //        ),
            //        "msg" => ""
            //    }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_funding_rates($data, $symbols);
        }) ();
    }

    public function fetch_funding_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetch the history of funding payments paid and received on this account
             *
             * @see https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-bills-details-last-3-months
             *
             * @param {string} $symbol unified $market $symbol
             * @param {int} [$since] the earliest time in ms to fetch funding history for
             * @param {int} [$limit] the maximum number of funding history structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=funding-history-structure funding history structure~
             */
            Async\await($this->load_markets());
            $request = array(
                // '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 !== null) {
                $request['limit'] = (string) $limit; // default 100, max 100
            }
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $symbol = $market['symbol'];
                if ($market['contract']) {
                    if ($market['linear']) {
                        $request['ctType'] = 'linear';
                        $request['ccy'] = $market['quoteId'];
                    } else {
                        $request['ctType'] = 'inverse';
                        $request['ccy'] = $market['baseId'];
                    }
                }
            }
            list($type, $query) = $this->handle_market_type_and_params('fetchFundingHistory', $market, $params);
            if ($type === 'swap') {
                $request['instType'] = $this->convert_to_instrument_type($type);
            }
            // AccountBillsArchive has the same cost but supports three months of $data
            $response = Async\await($this->privateGetAccountBillsArchive ($this->extend($request, $query)));
            //
            //    {
            //        "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 = $this->safe_list($response, 'data', array());
            $result = array();
            for ($i = 0; $i < count($data); $i++) {
                $entry = $data[$i];
                $timestamp = $this->safe_integer($entry, 'ts');
                $instId = $this->safe_string($entry, 'instId');
                $marketInner = $this->safe_market($instId);
                $currencyId = $this->safe_string($entry, 'ccy');
                $code = $this->safe_currency_code($currencyId);
                $result[] = array(
                    'info' => $entry,
                    'symbol' => $marketInner['symbol'],
                    'code' => $code,
                    'timestamp' => $timestamp,
                    'datetime' => $this->iso8601($timestamp),
                    'id' => $this->safe_string($entry, 'billId'),
                    'amount' => $this->safe_number($entry, 'balChg'),
                );
            }
            $sorted = $this->sort_by($result, 'timestamp');
            return $this->filter_by_symbol_since_limit($sorted, $symbol, $since, $limit);
        }) ();
    }

    public function set_leverage(int $leverage, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($leverage, $symbol, $params) {
            /**
             * set the level of $leverage for a $market
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-account-set-$leverage
             *
             * @param {float} $leverage the rate of $leverage
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->marginMode] 'cross' or 'isolated'
             * @param {string} [$params->posSide] 'long' or 'short' or 'net' for isolated margin long/short mode on futures and swap markets, default is 'net'
             * @return {array} $response from the exchange
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->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) || ($leverage > 125)) {
                throw new BadRequest($this->id . ' setLeverage() $leverage should be between 1 and 125');
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $marginMode = null;
            list($marginMode, $params) = $this->handle_margin_mode_and_params('setLeverage', $params);
            if ($marginMode === null) {
                $marginMode = $this->safe_string($params, 'mgnMode', 'cross'); // cross $marginMode
            }
            if (($marginMode !== 'cross') && ($marginMode !== 'isolated')) {
                throw new BadRequest($this->id . ' setLeverage() requires a $marginMode parameter that must be either cross or isolated');
            }
            $request = array(
                'lever' => $leverage,
                'mgnMode' => $marginMode,
                'instId' => $market['id'],
            );
            $posSide = $this->safe_string($params, 'posSide', 'net');
            if ($marginMode === 'isolated') {
                if ($posSide !== 'long' && $posSide !== 'short' && $posSide !== 'net') {
                    throw new BadRequest($this->id . ' setLeverage() requires the $posSide argument to be either "long", "short" or "net"');
                }
                $request['posSide'] = $posSide;
            }
            $response = Async\await($this->privatePostAccountSetLeverage ($this->extend($request, $params)));
            //
            //     {
            //       "code" => "0",
            //       "data" => array(
            //         {
            //           "instId" => "BTC-USDT-SWAP",
            //           "lever" => "5",
            //           "mgnMode" => "isolated",
            //           "posSide" => "long"
            //         }
            //       ),
            //       "msg" => ""
            //     }
            //
            return $response;
        }) ();
    }

    public function fetch_position_mode(?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             *
             * @see https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-account-configuration
             *
             * fetchs the position mode, hedged or one way, hedged for binance is set identically for all linear markets or all inverse markets
             * @param {string} $symbol unified $symbol of the market to fetch the order book for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->accountId] if you have multiple $accounts, you must specify the account id to fetch the position mode
             * @return {array} an object detailing whether the market is in hedged or one-way mode
             */
            $accounts = Async\await($this->fetch_accounts());
            $length = count($accounts);
            $selectedAccount = null;
            if ($length > 1) {
                $accountId = $this->safe_string($params, 'accountId');
                if ($accountId === null) {
                    $accountIds = $this->get_list_from_object_values($accounts, 'id');
                    throw new ExchangeError($this->id . ' fetchPositionMode() can not detect position mode, because you have multiple $accounts-> Set $params["accountId"] to desired id from => ' . implode(', ', $accountIds));
                } else {
                    $accountsById = $this->index_by($accounts, 'id');
                    $selectedAccount = $this->safe_dict($accountsById, $accountId);
                }
            } else {
                $selectedAccount = $accounts[0];
            }
            $mainAccount = $selectedAccount['info'];
            $posMode = $this->safe_string($mainAccount, 'posMode'); // long_short_mode, net_mode
            $isHedged = $posMode === 'long_short_mode';
            return array(
                'info' => $mainAccount,
                'hedged' => $isHedged,
            );
        }) ();
    }

    public function set_position_mode(bool $hedged, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($hedged, $symbol, $params) {
            /**
             * set $hedged to true or false for a market
             *
             * @see https://www.okx.com/docs-v5/en/#trading-account-rest-api-set-position-mode
             *
             * @param {bool} $hedged set to true to use long_short_mode, false for net_mode
             * @param {string} $symbol not used by okx setPositionMode
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} $response from the exchange
             */
            $hedgeMode = null;
            if ($hedged) {
                $hedgeMode = 'long_short_mode';
            } else {
                $hedgeMode = 'net_mode';
            }
            $request = array(
                'posMode' => $hedgeMode,
            );
            $response = Async\await($this->privatePostAccountSetPositionMode ($this->extend($request, $params)));
            //
            //    {
            //        "code" => "0",
            //        "data" => array(
            //            {
            //                "posMode" => "net_mode"
            //            }
            //        ),
            //        "msg" => ""
            //    }
            //
            return $response;
        }) ();
    }

    public function set_margin_mode(string $marginMode, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($marginMode, $symbol, $params) {
            /**
             * set margin mode to 'cross' or 'isolated'
             *
             * @see https://www.okx.com/docs-v5/en/#trading-account-rest-api-set-leverage
             *
             * @param {string} $marginMode 'cross' or 'isolated'
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->leverage] leverage
             * @return {array} $response from the exchange
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' setMarginMode() requires a $symbol argument');
            }
            // WARNING => THIS WILL INCREASE LIQUIDATION PRICE FOR OPEN ISOLATED LONG POSITIONS
            // AND DECREASE LIQUIDATION PRICE FOR OPEN ISOLATED SHORT POSITIONS
            $marginMode = strtolower($marginMode);
            if (($marginMode !== 'cross') && ($marginMode !== 'isolated')) {
                throw new BadRequest($this->id . ' setMarginMode() $marginMode must be either cross or isolated');
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $lever = $this->safe_integer_2($params, 'lever', 'leverage');
            if (($lever === null) || ($lever < 1) || ($lever > 125)) {
                throw new BadRequest($this->id . ' setMarginMode() $params["lever"] should be between 1 and 125');
            }
            $params = $this->omit($params, array( 'leverage' ));
            $request = array(
                'lever' => $lever,
                'mgnMode' => $marginMode,
                'instId' => $market['id'],
            );
            $response = Async\await($this->privatePostAccountSetLeverage ($this->extend($request, $params)));
            //
            //     {
            //       "code" => "0",
            //       "data" => array(
            //         {
            //           "instId" => "BTC-USDT-SWAP",
            //           "lever" => "5",
            //           "mgnMode" => "isolated",
            //           "posSide" => "long"
            //         }
            //       ),
            //       "msg" => ""
            //     }
            //
            return $response;
        }) ();
    }

    public function fetch_cross_borrow_rates($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetch the borrow interest $rates of all currencies
             *
             * @see https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-interest-rate
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a list of ~@link https://docs.ccxt.com/#/?id=borrow-rate-structure borrow rate structures~
             */
            Async\await($this->load_markets());
            $response = Async\await($this->privateGetAccountInterestRate ($params));
            //
            //    {
            //        "code" => "0",
            //        "data" => array(
            //            {
            //                "ccy" => "BTC",
            //                "interestRate" => "0.00000833"
            //            }
            //            ...
            //        ),
            //    }
            //
            $data = $this->safe_list($response, 'data', array());
            $rates = array();
            for ($i = 0; $i < count($data); $i++) {
                $rates[] = $this->parse_borrow_rate($data[$i]);
            }
            return $rates;
        }) ();
    }

    public function fetch_cross_borrow_rate(string $code, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $params) {
            /**
             * fetch the $rate of interest to borrow a $currency for margin trading
             *
             * @see https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-interest-$rate
             *
             * @param {string} $code unified $currency $code
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=borrow-$rate-structure borrow $rate structure~
             */
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            $request = array(
                'ccy' => $currency['id'],
            );
            $response = Async\await($this->privateGetAccountInterestRate ($this->extend($request, $params)));
            //
            //    {
            //        "code" => "0",
            //        "data" => array(
            //             {
            //                "ccy" => "USDT",
            //                "interestRate" => "0.00002065"
            //             }
            //             ...
            //        ),
            //        "msg" => ""
            //    }
            //
            $data = $this->safe_list($response, 'data', array());
            $rate = $this->safe_dict($data, 0, array());
            return $this->parse_borrow_rate($rate);
        }) ();
    }

    public function parse_borrow_rate($info, ?array $currency = null) {
        //
        //    {
        //        "amt" => "992.10341195",
        //        "ccy" => "BTC",
        //        "rate" => "0.01",
        //        "ts" => "1643954400000"
        //    }
        //
        $ccy = $this->safe_string($info, 'ccy');
        $timestamp = $this->safe_integer($info, 'ts');
        return array(
            'currency' => $this->safe_currency_code($ccy),
            'rate' => $this->safe_number_2($info, 'interestRate', 'rate'),
            'period' => 86400000,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'info' => $info,
        );
    }

    public function parse_borrow_rate_histories($response, $codes, $since, $limit) {
        //
        //    array(
        //        array(
        //            "amt" => "992.10341195",
        //            "ccy" => "BTC",
        //            "rate" => "0.01",
        //            "ts" => "1643954400000"
        //        ),
        //        ...
        //    )
        //
        $borrowRateHistories = array();
        for ($i = 0; $i < count($response); $i++) {
            $item = $response[$i];
            $code = $this->safe_currency_code($this->safe_string($item, 'ccy'));
            if ($codes === null || $this->in_array($code, $codes)) {
                if (!(is_array($borrowRateHistories) && array_key_exists($code, $borrowRateHistories))) {
                    $borrowRateHistories[$code] = array();
                }
                $borrowRateStructure = $this->parse_borrow_rate($item);
                $borrrowRateCode = $borrowRateHistories[$code];
                $borrrowRateCode[] = $borrowRateStructure;
            }
        }
        $keys = is_array($borrowRateHistories) ? array_keys($borrowRateHistories) : array();
        for ($i = 0; $i < count($keys); $i++) {
            $code = $keys[$i];
            $borrowRateHistories[$code] = $this->filter_by_currency_since_limit($borrowRateHistories[$code], $code, $since, $limit);
        }
        return $borrowRateHistories;
    }

    public function fetch_borrow_rate_histories($codes = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($codes, $since, $limit, $params) {
            /**
             * retrieves a history of a multiple currencies borrow interest rate at specific time slots, returns all currencies if no symbols passed, default is null
             *
             * @see https://www.okx.com/docs-v5/en/#financial-product-savings-get-public-borrow-history-public
             *
             * @param {string[]|null} $codes list of unified currency $codes, default is null
             * @param {int} [$since] timestamp in ms of the earliest borrowRate, default is null
             * @param {int} [$limit] max number of borrow rate prices to return, default is null
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=borrow-rate-structure borrow rate structures~ indexed by the market symbol
             */
            Async\await($this->load_markets());
            $request = array(
                // 'ccy' => currency['id'],
                // 'after' => $this->milliseconds(), // Pagination of $data to return records earlier than the requested ts,
                // 'before' => $since, // Pagination of $data to return records newer than the requested ts,
                // 'limit' => $limit, // default is 100 and maximum is 100
            );
            if ($since !== null) {
                $request['before'] = $since;
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $response = Async\await($this->publicGetFinanceSavingsLendingRateHistory ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             array(
            //                 "amt" => "992.10341195",
            //                 "ccy" => "BTC",
            //                 "rate" => "0.01",
            //                 "ts" => "1643954400000"
            //             ),
            //         ),
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_borrow_rate_histories($data, $codes, $since, $limit);
        }) ();
    }

    public function fetch_borrow_rate_history(string $code, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * retrieves a history of a currencies borrow interest rate at specific time slots
             *
             * @see https://www.okx.com/docs-v5/en/#financial-product-savings-get-public-borrow-history-public
             *
             * @param {string} $code unified $currency $code
             * @param {int} [$since] timestamp for the earliest borrow rate
             * @param {int} [$limit] the maximum number of ~@link https://docs.ccxt.com/#/?id=borrow-rate-structure borrow rate structures~ to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} an array of ~@link https://docs.ccxt.com/#/?id=borrow-rate-structure borrow rate structures~
             */
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            $request = array(
                'ccy' => $currency['id'],
                // 'after' => $this->milliseconds(), // Pagination of $data to return records earlier than the requested ts,
                // 'before' => $since, // Pagination of $data to return records newer than the requested ts,
                // 'limit' => $limit, // default is 100 and maximum is 100
            );
            if ($since !== null) {
                $request['before'] = $since;
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $response = Async\await($this->publicGetFinanceSavingsLendingRateHistory ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             array(
            //                 "amt" => "992.10341195",
            //                 "ccy" => "BTC",
            //                 "rate" => "0.01",
            //                 "ts" => "1643954400000"
            //             ),
            //         ),
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_borrow_rate_history($data, $code, $since, $limit);
        }) ();
    }

    public function modify_margin_helper(string $symbol, $amount, $type, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $amount, $type, $params) {
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $posSide = $this->safe_string($params, 'posSide', 'net');
            $params = $this->omit($params, array( 'posSide' ));
            $request = array(
                'instId' => $market['id'],
                'amt' => $amount,
                'type' => $type,
                'posSide' => $posSide,
            );
            $response = Async\await($this->privatePostAccountPositionMarginBalance ($this->extend($request, $params)));
            //
            //     {
            //       "code" => "0",
            //       "data" => array(
            //         {
            //           "amt" => "0.01",
            //           "instId" => "ETH-USD-SWAP",
            //           "posSide" => "net",
            //           "type" => "reduce"
            //         }
            //       ),
            //       "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $entry = $this->safe_dict($data, 0, array());
            $errorCode = $this->safe_string($response, 'code');
            return $this->extend($this->parse_margin_modification($entry, $market), array(
                'status' => ($errorCode === '0') ? 'ok' : 'failed',
            ));
        }) ();
    }

    public function parse_margin_modification(array $data, ?array $market = null): array {
        //
        // addMargin/reduceMargin
        //
        //    {
        //        "amt" => "0.01",
        //        "instId" => "ETH-USD-SWAP",
        //        "posSide" => "net",
        //        "type" => "reduce"
        //    }
        //
        // fetchMarginAdjustmentHistory
        //
        //    {
        //        bal => '67621.4325135010619812',
        //        balChg => '-10.0000000000000000',
        //        billId => '691293628710342659',
        //        ccy => 'USDT',
        //        clOrdId => '',
        //        execType => '',
        //        fee => '0',
        //        fillFwdPx => '',
        //        fillIdxPx => '',
        //        fillMarkPx => '',
        //        fillMarkVol => '',
        //        fillPxUsd => '',
        //        fillPxVol => '',
        //        fillTime => '1711089244850',
        //        from => '',
        //        instId => 'XRP-USDT-SWAP',
        //        instType => 'SWAP',
        //        interest => '0',
        //        mgnMode => 'isolated',
        //        notes => '',
        //        ordId => '',
        //        pnl => '0',
        //        posBal => '73.12',
        //        posBalChg => '10.00',
        //        px => '',
        //        subType => '160',
        //        sz => '10',
        //        tag => '',
        //        to => '',
        //        tradeId => '0',
        //        ts => '1711089244699',
        //        $type => '6'
        //    }
        //
        $amountRaw = $this->safe_string_2($data, 'amt', 'posBalChg');
        $typeRaw = $this->safe_string($data, 'type');
        $type = null;
        if ($typeRaw === '6') {
            $type = Precise::string_gt($amountRaw, '0') ? 'add' : 'reduce';
        } else {
            $type = $typeRaw;
        }
        $amount = Precise::string_abs($amountRaw);
        $marketId = $this->safe_string($data, 'instId');
        $responseMarket = $this->safe_market($marketId, $market);
        $code = $responseMarket['inverse'] ? $responseMarket['base'] : $responseMarket['quote'];
        $timestamp = $this->safe_integer($data, 'ts');
        return array(
            'info' => $data,
            'symbol' => $responseMarket['symbol'],
            'type' => $type,
            'marginMode' => 'isolated',
            'amount' => $this->parse_number($amount),
            'code' => $code,
            'total' => null,
            'status' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
        );
    }

    public function reduce_margin(string $symbol, float $amount, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $amount, $params) {
            /**
             * remove margin from a position
             *
             * @see https://www.okx.com/docs-v5/en/#trading-account-rest-api-increase-decrease-margin
             *
             * @param {string} $symbol unified market $symbol
             * @param {float} $amount the $amount of margin to remove
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=reduce-margin-structure margin structure~
             */
            return Async\await($this->modify_margin_helper($symbol, $amount, 'reduce', $params));
        }) ();
    }

    public function add_margin(string $symbol, float $amount, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $amount, $params) {
            /**
             * add margin
             *
             * @see https://www.okx.com/docs-v5/en/#trading-account-rest-api-increase-decrease-margin
             *
             * @param {string} $symbol unified market $symbol
             * @param {float} $amount amount of margin to add
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=add-margin-structure margin structure~
             */
            return Async\await($this->modify_margin_helper($symbol, $amount, 'add', $params));
        }) ();
    }

    public function fetch_market_leverage_tiers(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * retrieve information on the maximum leverage, and maintenance margin for trades of varying trade sizes for a single $market
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-public-$data-get-position-tiers
             *
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->marginMode] 'cross' or 'isolated'
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=leverage-tiers-structure leverage tiers structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $type = $market['spot'] ? 'MARGIN' : $this->convert_to_instrument_type($market['type']);
            $uly = $this->safe_string($market['info'], 'uly');
            if (!$uly) {
                if ($type !== 'MARGIN') {
                    throw new BadRequest($this->id . ' fetchMarketLeverageTiers() cannot fetch leverage tiers for ' . $symbol);
                }
            }
            $marginMode = null;
            list($marginMode, $params) = $this->handle_margin_mode_and_params('fetchMarketLeverageTiers', $params);
            if ($marginMode === null) {
                $marginMode = $this->safe_string($params, 'tdMode', 'cross'); // cross $marginMode
            }
            $request = array(
                'instType' => $type,
                'tdMode' => $marginMode,
                'uly' => $uly,
            );
            if ($type === 'MARGIN') {
                $request['instId'] = $market['id'];
            }
            $response = Async\await($this->publicGetPublicPositionTiers ($this->extend($request, $params)));
            //
            //    {
            //        "code" => "0",
            //        "data" => array(
            //            array(
            //                "baseMaxLoan" => "500",
            //                "imr" => "0.1",
            //                "instId" => "ETH-USDT",
            //                "maxLever" => "10",
            //                "maxSz" => "500",
            //                "minSz" => "0",
            //                "mmr" => "0.03",
            //                "optMgnFactor" => "0",
            //                "quoteMaxLoan" => "200000",
            //                "tier" => "1",
            //                "uly" => ""
            //            ),
            //            ...
            //        )
            //    }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_market_leverage_tiers($data, $market);
        }) ();
    }

    public function parse_market_leverage_tiers($info, ?array $market = null): array {
        /**
         * @ignore
         * @param {array} $info Exchange response for 1 $market
         * @param {array} $market CCXT $market
         */
        //
        //    array(
        //        array(
        //            "baseMaxLoan" => "500",
        //            "imr" => "0.1",
        //            "instId" => "ETH-USDT",
        //            "maxLever" => "10",
        //            "maxSz" => "500",
        //            "minSz" => "0",
        //            "mmr" => "0.03",
        //            "optMgnFactor" => "0",
        //            "quoteMaxLoan" => "200000",
        //            "tier" => "1",
        //            "uly" => ""
        //        ),
        //        ...
        //    )
        //
        $tiers = array();
        for ($i = 0; $i < count($info); $i++) {
            $tier = $info[$i];
            $marketId = $this->safe_string($tier, 'instId');
            $tiers[] = array(
                'tier' => $this->safe_integer($tier, 'tier'),
                'symbol' => $this->safe_symbol($marketId, $market),
                'currency' => $market['quote'],
                'minNotional' => $this->safe_number($tier, 'minSz'),
                'maxNotional' => $this->safe_number($tier, 'maxSz'),
                'maintenanceMarginRate' => $this->safe_number($tier, 'mmr'),
                'maxLeverage' => $this->safe_number($tier, 'maxLever'),
                'info' => $tier,
            );
        }
        return $tiers;
    }

    public function fetch_borrow_interest(?string $code = null, ?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $symbol, $since, $limit, $params) {
            /**
             * fetch the $interest owed b the user for borrowing $currency for margin trading
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-account-get-$interest-accrued-$data
             *
             * @param {string} $code the unified $currency $code for the $currency of the $interest
             * @param {string} $symbol the $market $symbol of an isolated margin $market, if null, the $interest for cross margin markets is returned
             * @param {int} [$since] timestamp in ms of the earliest time to receive $interest records for
             * @param {int} [$limit] the number of ~@link https://docs.ccxt.com/#/?id=borrow-$interest-structure borrow $interest structures~ to retrieve
             * @param {array} [$params] exchange specific parameters
             * @param {int} [$params->type] Loan type 1 - VIP loans 2 - Market loans *Default is Market loans*
             * @param {string} [$params->marginMode] 'cross' or 'isolated'
             * @return {array[]} An list of ~@link https://docs.ccxt.com/#/?id=borrow-$interest-structure borrow $interest structures~
             */
            Async\await($this->load_markets());
            $marginMode = null;
            list($marginMode, $params) = $this->handle_margin_mode_and_params('fetchBorrowInterest', $params);
            if ($marginMode === null) {
                $marginMode = $this->safe_string($params, 'mgnMode', 'cross'); // cross $marginMode
            }
            $request = array(
                'mgnMode' => $marginMode,
            );
            $market = null;
            if ($code !== null) {
                $currency = $this->currency($code);
                $request['ccy'] = $currency['id'];
            }
            if ($since !== null) {
                $request['before'] = $since - 1;
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['instId'] = $market['id'];
            }
            $response = Async\await($this->privateGetAccountInterestAccrued ($this->extend($request, $params)));
            //
            //    {
            //        "code" => "0",
            //        "data" => array(
            //            array(
            //                "ccy" => "USDT",
            //                "instId" => "",
            //                "interest" => "0.0003960833333334",
            //                "interestRate" => "0.0000040833333333",
            //                "liab" => "97",
            //                "mgnMode" => "",
            //                "ts" => "1637312400000",
            //                "type" => "1"
            //            ),
            //            ...
            //        ),
            //        "msg" => ""
            //    }
            //
            $data = $this->safe_list($response, 'data', array());
            $interest = $this->parse_borrow_interests($data);
            return $this->filter_by_currency_since_limit($interest, $code, $since, $limit);
        }) ();
    }

    public function parse_borrow_interest(array $info, ?array $market = null): array {
        $instId = $this->safe_string($info, 'instId');
        if ($instId !== null) {
            $market = $this->safe_market($instId, $market);
        }
        $timestamp = $this->safe_integer($info, 'ts');
        return array(
            'info' => $info,
            'symbol' => $this->safe_string($market, 'symbol'),
            'currency' => $this->safe_currency_code($this->safe_string($info, 'ccy')),
            'interest' => $this->safe_number($info, 'interest'),
            'interestRate' => $this->safe_number($info, 'interestRate'),
            'amountBorrowed' => $this->safe_number($info, 'liab'),
            'marginMode' => $this->safe_string($info, 'mgnMode'),
            'timestamp' => $timestamp,  // Interest accrued time
            'datetime' => $this->iso8601($timestamp),
        );
    }

    public function borrow_cross_margin(string $code, float $amount, $params = array ()) {
        return Async\async(function () use ($code, $amount, $params) {
            /**
             * create a $loan to borrow margin (need to be VIP 5 and above)
             *
             * @see https://www.okx.com/docs-v5/en/#trading-account-rest-api-vip-loans-borrow-and-repay
             *
             * @param {string} $code unified $currency $code of the $currency to borrow
             * @param {float} $amount the $amount to borrow
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=margin-$loan-structure margin $loan structure~
             */
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            $request = array(
                'ccy' => $currency['id'],
                'amt' => $this->currency_to_precision($code, $amount),
                'side' => 'borrow',
            );
            $response = Async\await($this->privatePostAccountBorrowRepay ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "amt" => "102",
            //                 "ccy" => "USDT",
            //                 "ordId" => "544199684697214976",
            //                 "side" => "borrow",
            //                 "state" => "1"
            //             }
            //         ),
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $loan = $this->safe_dict($data, 0, array());
            return $this->parse_margin_loan($loan, $currency);
        }) ();
    }

    public function repay_cross_margin(string $code, $amount, $params = array ()) {
        return Async\async(function () use ($code, $amount, $params) {
            /**
             * repay borrowed margin and interest
             *
             * @see https://www.okx.com/docs-v5/en/#trading-account-rest-api-vip-loans-borrow-and-repay
             *
             * @param {string} $code unified $currency $code of the $currency to repay
             * @param {float} $amount the $amount to repay
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->id] the order ID of borrowing, it is necessary while repaying
             * @return {array} a ~@link https://docs.ccxt.com/#/?$id=margin-$loan-structure margin $loan structure~
             */
            Async\await($this->load_markets());
            $id = $this->safe_string_2($params, 'id', 'ordId');
            $params = $this->omit($params, 'id');
            if ($id === null) {
                throw new ArgumentsRequired($this->id . ' repayCrossMargin() requires an $id parameter');
            }
            $currency = $this->currency($code);
            $request = array(
                'ccy' => $currency['id'],
                'amt' => $this->currency_to_precision($code, $amount),
                'side' => 'repay',
                'ordId' => $id,
            );
            $response = Async\await($this->privatePostAccountBorrowRepay ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "amt" => "102",
            //                 "ccy" => "USDT",
            //                 "ordId" => "544199684697214976",
            //                 "side" => "repay",
            //                 "state" => "1"
            //             }
            //         ),
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $loan = $this->safe_dict($data, 0, array());
            return $this->parse_margin_loan($loan, $currency);
        }) ();
    }

    public function parse_margin_loan($info, ?array $currency = null) {
        //
        //     {
        //         "amt" => "102",
        //         "availLoan" => "97",
        //         "ccy" => "USDT",
        //         "loanQuota" => "6000000",
        //         "posLoan" => "0",
        //         "side" => "repay",
        //         "usedLoan" => "97"
        //     }
        //
        $currencyId = $this->safe_string($info, 'ccy');
        return array(
            'id' => null,
            'currency' => $this->safe_currency_code($currencyId, $currency),
            'amount' => $this->safe_number($info, 'amt'),
            'symbol' => null,
            'timestamp' => null,
            'datetime' => null,
            'info' => $info,
        );
    }

    public function fetch_open_interest(string $symbol, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             * Retrieves the open interest of a currency
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-public-$data-get-open-interest
             *
             * @param {string} $symbol Unified CCXT $market $symbol
             * @param {array} [$params] exchange specific parameters
             * @return {array} an open interest structurearray(@link https://docs.ccxt.com/#/?id=open-interest-structure)
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            if (!$market['contract']) {
                throw new BadRequest($this->id . ' fetchOpenInterest() supports contract markets only');
            }
            $type = $this->convert_to_instrument_type($market['type']);
            $uly = $this->safe_string($market['info'], 'uly');
            $request = array(
                'instType' => $type,
                'uly' => $uly,
                'instId' => $market['id'],
            );
            $response = Async\await($this->publicGetPublicOpenInterest ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "instId" => "BTC-USDT-SWAP",
            //                 "instType" => "SWAP",
            //                 "oi" => "2125419",
            //                 "oiCcy" => "21254.19",
            //                 "ts" => "1664005108969"
            //             }
            //         ),
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_open_interest($data[0], $market);
        }) ();
    }

    public function fetch_open_interests(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * Retrieves the open interests of some currencies
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-public-$data-get-open-interest
             *
             * @param {string[]} $symbols Unified CCXT $market $symbols
             * @param {array} [$params] exchange specific parameters
             * @param {string} $params->instType Instrument type, options => 'SWAP', 'FUTURES', 'OPTION', default to 'SWAP'
             * @param {string} $params->uly Underlying, Applicable to FUTURES/SWAP/OPTION, if $instType is 'OPTION', either $uly or $instFamily is required
             * @param {string} $params->instFamily Instrument family, Applicable to FUTURES/SWAP/OPTION, if $instType is 'OPTION', either $uly or $instFamily is required
             * @return {array} an dictionary of ~@link https://docs.ccxt.com/#/?id=open-interest-structure open interest structures~
             */
            Async\await($this->load_markets());
            $symbols = $this->market_symbols($symbols, null, true, true);
            $market = null;
            if ($symbols !== null) {
                $market = $this->market($symbols[0]);
            }
            $marketType = null;
            list($marketType, $params) = $this->handle_sub_type_and_params('fetchOpenInterests', $market, $params, 'swap');
            $instType = 'SWAP';
            if ($marketType === 'future') {
                $instType = 'FUTURES';
            } elseif ($instType === 'option') {
                $instType = 'OPTION';
            }
            $request = array( 'instType' => $instType );
            $uly = $this->safe_string($params, 'uly');
            if ($uly !== null) {
                $request['uly'] = $uly;
            }
            $instFamily = $this->safe_string($params, 'instFamily');
            if ($instFamily !== null) {
                $request['instFamily'] = $instFamily;
            }
            if ($instType === 'OPTION' && $uly === null && $instFamily === null) {
                throw new BadRequest($this->id . ' fetchOpenInterests() requires either $uly or $instFamily parameter for OPTION markets');
            }
            $response = Async\await($this->publicGetPublicOpenInterest ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "instId" => "BTC-USDT-SWAP",
            //                 "instType" => "SWAP",
            //                 "oi" => "2125419",
            //                 "oiCcy" => "21254.19",
            //                 "ts" => "1664005108969"
            //             }
            //         ),
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_open_interests($data, $symbols);
        }) ();
    }

    public function fetch_open_interest_history(string $symbol, $timeframe = '1d', ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $timeframe, $since, $limit, $params) {
            /**
             * Retrieves the open interest history of a $currency
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-trading-$data-get-contracts-open-interest-and-volume
             * @see https://www.okx.com/docs-v5/en/#rest-api-trading-$data-get-$options-open-interest-and-volume
             *
             * @param {string} $symbol Unified CCXT $currency code or unified $symbol
             * @param {string} $timeframe "5m", "1h", or "1d" for option only "1d" or "8h"
             * @param {int} [$since] The time in ms of the earliest record to retrieve unix timestamp
             * @param {int} [$limit] Not used by okx, but parsed internally by CCXT
             * @param {array} [$params] Exchange specific parameters
             * @param {int} [$params->until] The time in ms of the latest record to retrieve unix timestamp
             * @return An array of ~@link https://docs.ccxt.com/#/?id=open-interest-structure open interest structures~
             */
            $options = $this->safe_dict($this->options, 'fetchOpenInterestHistory', array());
            $timeframes = $this->safe_dict($options, 'timeframes', array());
            $timeframe = $this->safe_string($timeframes, $timeframe, $timeframe);
            if ($timeframe !== '5m' && $timeframe !== '1H' && $timeframe !== '1D') {
                throw new BadRequest($this->id . ' fetchOpenInterestHistory cannot only use the 5m, 1h, and 1d timeframe');
            }
            Async\await($this->load_markets());
            // handle unified $currency code or $symbol
            $currencyId = null;
            $market = null;
            if ((is_array($this->markets) && array_key_exists($symbol, $this->markets)) || (is_array($this->markets_by_id) && array_key_exists($symbol, $this->markets_by_id))) {
                $market = $this->market($symbol);
                $currencyId = $market['baseId'];
            } else {
                $currency = $this->currency($symbol);
                $currencyId = $currency['id'];
            }
            $request = array(
                'ccy' => $currencyId,
                'period' => $timeframe,
            );
            $type = null;
            $response = null;
            list($type, $params) = $this->handle_market_type_and_params('fetchOpenInterestHistory', $market, $params);
            if ($type === 'option') {
                $response = Async\await($this->publicGetRubikStatOptionOpenInterestVolume ($this->extend($request, $params)));
            } else {
                if ($since !== null) {
                    $request['begin'] = $since;
                }
                $until = $this->safe_integer($params, 'until');
                if ($until !== null) {
                    $request['end'] = $until;
                    $params = $this->omit($params, array( 'until' ));
                }
                $response = Async\await($this->publicGetRubikStatContractsOpenInterestVolume ($this->extend($request, $params)));
            }
            //
            //    {
            //        "code" => "0",
            //        "data" => array(
            //            array(
            //                "1648221300000",  // timestamp
            //                "2183354317.945",  // open interest (USD)
            //                "74285877.617",  // volume (USD)
            //            ),
            //            ...
            //        ),
            //        "msg" => ''
            //    }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_open_interests_history($data, null, $since, $limit);
        }) ();
    }

    public function parse_open_interest($interest, ?array $market = null) {
        //
        // fetchOpenInterestHistory
        //
        //    array(
        //        "1648221300000",  // $timestamp
        //        "2183354317.945",  // open $interest (USD) - (coin) for options
        //        "74285877.617",  // volume (USD) - (coin) for options
        //    )
        //
        // fetchOpenInterest
        //
        //     {
        //         "instId" => "BTC-USD-230520-25500-P",
        //         "instType" => "OPTION",
        //         "oi" => "300",
        //         "oiCcy" => "3",
        //         "oiUsd" => "3",
        //         "ts" => "1684551166251"
        //     }
        //
        $id = $this->safe_string($interest, 'instId');
        $market = $this->safe_market($id, $market);
        $time = $this->safe_integer($interest, 'ts');
        $timestamp = $this->safe_integer($interest, 0, $time);
        $baseVolume = null;
        $quoteVolume = null;
        $openInterestAmount = null;
        $openInterestValue = null;
        $type = $this->safe_string($this->options, 'defaultType');
        if (gettype($interest) === 'array' && array_keys($interest) === array_keys(array_keys($interest))) {
            if ($type === 'option') {
                $openInterestAmount = $this->safe_number($interest, 1);
                $baseVolume = $this->safe_number($interest, 2);
            } else {
                $openInterestValue = $this->safe_number($interest, 1);
                $quoteVolume = $this->safe_number($interest, 2);
            }
        } else {
            $baseVolume = $this->safe_number($interest, 'oiCcy');
            $openInterestAmount = $this->safe_number($interest, 'oi');
            $openInterestValue = $this->safe_number($interest, 'oiUsd');
        }
        return $this->safe_open_interest(array(
            'symbol' => $this->safe_symbol($id),
            'baseVolume' => $baseVolume,  // deprecated
            'quoteVolume' => $quoteVolume,  // deprecated
            'openInterestAmount' => $openInterestAmount,
            'openInterestValue' => $openInterestValue,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'info' => $interest,
        ), $market);
    }

    public function set_sandbox_mode(bool $enable) {
        parent::set_sandbox_mode($enable);
        $this->options['sandboxMode'] = $enable;
        if ($enable) {
            $this->headers['x-simulated-trading'] = '1';
        } elseif (is_array($this->headers) && array_key_exists('x-simulated-trading', $this->headers)) {
            $this->headers = $this->omit($this->headers, 'x-simulated-trading');
        }
    }

    public function fetch_deposit_withdraw_fees(?array $codes = null, $params = array ()) {
        return Async\async(function () use ($codes, $params) {
            /**
             * fetch deposit and withdraw fees
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-funding-get-currencies
             *
             * @param {string[]|null} $codes list of unified currency $codes
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=fee-structure fees structures~
             */
            Async\await($this->load_markets());
            $request = array();
            if ($codes !== null) {
                $ids = $this->currency_ids($codes);
                $request['ccy'] = implode(',', $ids);
            }
            $response = Async\await($this->privateGetAssetCurrencies ($this->extend($request, $params)));
            //
            //    {
            //        "code" => "0",
            //        "data" => array(
            //            array(
            //                "canDep" => true,
            //                "canInternal" => false,
            //                "canWd" => true,
            //                "ccy" => "USDT",
            //                "chain" => "USDT-TRC20",
            //                "logoLink" => "https://static.coinall.ltd/cdn/assets/imgs/221/5F74EB20302D7761.png",
            //                "mainNet" => false,
            //                "maxFee" => "1.6",
            //                "maxWd" => "8852150",
            //                "minFee" => "0.8",
            //                "minWd" => "2",
            //                "name" => "Tether",
            //                "usedWdQuota" => "0",
            //                "wdQuota" => "500",
            //                "wdTickSz" => "3"
            //            ),
            //            array(
            //                "canDep" => true,
            //                "canInternal" => false,
            //                "canWd" => true,
            //                "ccy" => "USDT",
            //                "chain" => "USDT-ERC20",
            //                "logoLink" => "https://static.coinall.ltd/cdn/assets/imgs/221/5F74EB20302D7761.png",
            //                "mainNet" => false,
            //                "maxFee" => "16",
            //                "maxWd" => "8852150",
            //                "minFee" => "8",
            //                "minWd" => "2",
            //                "name" => "Tether",
            //                "usedWdQuota" => "0",
            //                "wdQuota" => "500",
            //                "wdTickSz" => "3"
            //            ),
            //            ...
            //        ),
            //        "msg" => ""
            //    }
            //
            $data = $this->safe_list($response, 'data');
            return $this->parse_deposit_withdraw_fees($data, $codes);
        }) ();
    }

    public function parse_deposit_withdraw_fees($response, $codes = null, $currencyIdKey = null) {
        //
        // array(
        //   {
        //       "canDep" => true,
        //       "canInternal" => false,
        //       "canWd" => true,
        //       "ccy" => "USDT",
        //       "chain" => "USDT-TRC20",
        //       "logoLink" => "https://static.coinall.ltd/cdn/assets/imgs/221/5F74EB20302D7761.png",
        //       "mainNet" => false,
        //       "maxFee" => "1.6",
        //       "maxWd" => "8852150",
        //       "minFee" => "0.8",
        //       "minWd" => "2",
        //       "name" => "Tether",
        //       "usedWdQuota" => "0",
        //       "wdQuota" => "500",
        //       "wdTickSz" => "3"
        //   }
        // )
        //
        $depositWithdrawFees = array();
        $codes = $this->market_codes($codes);
        for ($i = 0; $i < count($response); $i++) {
            $feeInfo = $response[$i];
            $currencyId = $this->safe_string($feeInfo, 'ccy');
            $code = $this->safe_currency_code($currencyId);
            if (($codes === null) || ($this->in_array($code, $codes))) {
                $depositWithdrawFee = $this->safe_value($depositWithdrawFees, $code);
                if ($depositWithdrawFee === null) {
                    $depositWithdrawFees[$code] = $this->deposit_withdraw_fee(array());
                }
                $depositWithdrawFees[$code]['info'][$currencyId] = $feeInfo;
                $chain = $this->safe_string($feeInfo, 'chain');
                if ($chain === null) {
                    continue;
                }
                $chainSplit = explode('-', $chain);
                $networkId = $this->safe_value($chainSplit, 1);
                $withdrawFee = $this->safe_number($feeInfo, 'fee');
                $withdrawResult = array(
                    'fee' => $withdrawFee,
                    'percentage' => ($withdrawFee !== null) ? false : null,
                );
                $depositResult = array(
                    'fee' => null,
                    'percentage' => null,
                );
                $networkCode = $this->network_id_to_code($networkId, $code);
                $depositWithdrawFees[$code]['networks'][$networkCode] = array(
                    'withdraw' => $withdrawResult,
                    'deposit' => $depositResult,
                );
            }
        }
        $depositWithdrawCodes = is_array($depositWithdrawFees) ? array_keys($depositWithdrawFees) : array();
        for ($i = 0; $i < count($depositWithdrawCodes); $i++) {
            $code = $depositWithdrawCodes[$i];
            $currency = $this->currency($code);
            $depositWithdrawFees[$code] = $this->assign_default_deposit_withdraw_fees($depositWithdrawFees[$code], $currency);
        }
        return $depositWithdrawFees;
    }

    public function fetch_settlement_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetches historical settlement records
             *
             * @see https://www.okx.com/docs-v5/en/#rest-api-public-$data-get-delivery-exercise-history
             *
             * @param {string} $symbol unified $market $symbol to fetch the settlement history for
             * @param {int} [$since] timestamp in ms
             * @param {int} [$limit] number of records
             * @param {array} [$params] exchange specific $params
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=settlement-history-structure settlement history objects~
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' fetchSettlementHistory() requires a $symbol argument');
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $type = null;
            list($type, $params) = $this->handle_market_type_and_params('fetchSettlementHistory', $market, $params);
            if ($type !== 'future' && $type !== 'option') {
                throw new NotSupported($this->id . ' fetchSettlementHistory() supports futures and options markets only');
            }
            $request = array(
                'instType' => $this->convert_to_instrument_type($type),
                'uly' => $market['baseId'] . '-' . $market['quoteId'],
            );
            if ($since !== null) {
                $request['before'] = $since - 1;
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $response = Async\await($this->publicGetPublicDeliveryExerciseHistory ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "details" => array(
            //                     array(
            //                         "insId" => "BTC-USD-230523-25750-C",
            //                         "px" => "27290.1486867000556483",
            //                         "type" => "exercised"
            //                     ),
            //                 ),
            //                 "ts":"1684656000000"
            //             }
            //         ),
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $settlements = $this->parse_settlements($data, $market);
            $sorted = $this->sort_by($settlements, 'timestamp');
            return $this->filter_by_symbol_since_limit($sorted, $market['symbol'], $since, $limit);
        }) ();
    }

    public function parse_settlement($settlement, $market) {
        //
        //     {
        //         "insId" => "BTC-USD-230521-28500-P",
        //         "px" => "27081.2007345984751516",
        //         "type" => "exercised"
        //     }
        //
        $marketId = $this->safe_string($settlement, 'insId');
        return array(
            'info' => $settlement,
            'symbol' => $this->safe_symbol($marketId, $market),
            'price' => $this->safe_number($settlement, 'px'),
            'timestamp' => null,
            'datetime' => null,
        );
    }

    public function parse_settlements($settlements, $market) {
        //
        //     {
        //         "details" => array(
        //             array(
        //                 "insId" => "BTC-USD-230523-25750-C",
        //                 "px" => "27290.1486867000556483",
        //                 "type" => "exercised"
        //             ),
        //         ),
        //         "ts":"1684656000000"
        //     }
        //
        $result = array();
        for ($i = 0; $i < count($settlements); $i++) {
            $entry = $settlements[$i];
            $timestamp = $this->safe_integer($entry, 'ts');
            $details = $this->safe_list($entry, 'details', array());
            for ($j = 0; $j < count($details); $j++) {
                $settlement = $this->parse_settlement($details[$j], $market);
                $result[] = $this->extend($settlement, array(
                    'timestamp' => $timestamp,
                    'datetime' => $this->iso8601($timestamp),
                ));
            }
        }
        return $result;
    }

    public function fetch_underlying_assets($params = array ()) {
        return Async\async(function () use ($params) {
            /**
             * fetches the market ids of underlying assets for a specific contract market type
             *
             * @see https://www.okx.com/docs-v5/en/#public-data-rest-api-get-underlying
             *
             * @param {array} [$params] exchange specific $params
             * @param {string} [$params->type] the contract market type, 'option', 'swap' or 'future', the default is 'option'
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=underlying-assets-structure underlying assets~
             */
            Async\await($this->load_markets());
            $marketType = null;
            list($marketType, $params) = $this->handle_market_type_and_params('fetchUnderlyingAssets', null, $params);
            if (($marketType === null) || ($marketType === 'spot')) {
                $marketType = 'option';
            }
            if (($marketType !== 'option') && ($marketType !== 'swap') && ($marketType !== 'future')) {
                throw new NotSupported($this->id . ' fetchUnderlyingAssets() supports contract markets only');
            }
            $request = array(
                'instType' => $this->convert_to_instrument_type($marketType),
            );
            $response = Async\await($this->publicGetPublicUnderlying ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             array(
            //                 "BTC-USD",
            //                 "ETH-USD"
            //             )
            //         ),
            //         "msg" => ""
            //     }
            //
            $underlyings = $this->safe_list($response, 'data', array());
            return $underlyings[0];
        }) ();
    }

    public function fetch_greeks(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetches an option contracts greeks, financial metrics used to measure the factors that affect the price of an options contract
             *
             * @see https://www.okx.com/docs-v5/en/#public-$data-rest-api-get-option-$market-$data
             *
             * @param {string} $symbol unified $symbol of the $market to fetch greeks for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=greeks-structure greeks structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $marketId = $market['id'];
            $optionParts = explode('-', $marketId);
            $request = array(
                'uly' => $market['info']['uly'],
                'instFamily' => $market['info']['instFamily'],
                'expTime' => $this->safe_string($optionParts, 2),
            );
            $response = Async\await($this->publicGetPublicOptSummary ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             array(
            //                 "askVol" => "0",
            //                 "bidVol" => "0",
            //                 "delta" => "0.5105464486882039",
            //                 "deltaBS" => "0.7325502184143025",
            //                 "fwdPx" => "37675.80158694987186",
            //                 "gamma" => "-0.13183515090501083",
            //                 "gammaBS" => "0.000024139685826358558",
            //                 "instId" => "BTC-USD-240329-32000-C",
            //                 "instType" => "OPTION",
            //                 "lever" => "4.504428015946619",
            //                 "markVol" => "0.5916253554539876",
            //                 "realVol" => "0",
            //                 "theta" => "-0.0004202992014012855",
            //                 "thetaBS" => "-18.52354631567909",
            //                 "ts" => "1699586421976",
            //                 "uly" => "BTC-USD",
            //                 "vega" => "0.0020207455080045846",
            //                 "vegaBS" => "74.44022302387287",
            //                 "volLv" => "0.5948549730405797"
            //             ),
            //         ),
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            for ($i = 0; $i < count($data); $i++) {
                $entry = $data[$i];
                $entryMarketId = $this->safe_string($entry, 'instId');
                if ($entryMarketId === $marketId) {
                    return $this->parse_greeks($entry, $market);
                }
            }
            return null;
        }) ();
    }

    public function fetch_all_greeks(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetches all option contracts greeks, financial metrics used to measure the factors that affect the price of an options contract
             *
             * @see https://www.okx.com/docs-v5/en/#public-$data-rest-api-get-option-$market-$data
             *
             * @param {string[]} [$symbols] unified $symbols of the markets to fetch greeks for, all markets are returned if not assigned
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} $params->uly Underlying, either $uly or $instFamily is required
             * @param {string} $params->instFamily Instrument family, either $uly or $instFamily is required
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=greeks-structure greeks structure~
             */
            Async\await($this->load_markets());
            $request = array();
            $symbols = $this->market_symbols($symbols, null, true, true, true);
            $symbolsLength = null;
            if ($symbols !== null) {
                $symbolsLength = count($symbols);
            }
            if (($symbols === null) || ($symbolsLength !== 1)) {
                $uly = $this->safe_string($params, 'uly');
                if ($uly !== null) {
                    $request['uly'] = $uly;
                }
                $instFamily = $this->safe_string($params, 'instFamily');
                if ($instFamily !== null) {
                    $request['instFamily'] = $instFamily;
                }
                if (($uly === null) && ($instFamily === null)) {
                    throw new BadRequest($this->id . ' fetchAllGreeks() requires either a $uly or $instFamily parameter');
                }
            }
            $market = null;
            if ($symbols !== null) {
                if ($symbolsLength === 1) {
                    $market = $this->market($symbols[0]);
                    $marketId = $market['id'];
                    $optionParts = explode('-', $marketId);
                    $request['uly'] = $market['info']['uly'];
                    $request['instFamily'] = $market['info']['instFamily'];
                    $request['expTime'] = $this->safe_string($optionParts, 2);
                }
            }
            $params = $this->omit($params, array( 'uly', 'instFamily' ));
            $response = Async\await($this->publicGetPublicOptSummary ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             array(
            //                 "askVol" => "0",
            //                 "bidVol" => "0",
            //                 "delta" => "0.5105464486882039",
            //                 "deltaBS" => "0.7325502184143025",
            //                 "fwdPx" => "37675.80158694987186",
            //                 "gamma" => "-0.13183515090501083",
            //                 "gammaBS" => "0.000024139685826358558",
            //                 "instId" => "BTC-USD-240329-32000-C",
            //                 "instType" => "OPTION",
            //                 "lever" => "4.504428015946619",
            //                 "markVol" => "0.5916253554539876",
            //                 "realVol" => "0",
            //                 "theta" => "-0.0004202992014012855",
            //                 "thetaBS" => "-18.52354631567909",
            //                 "ts" => "1699586421976",
            //                 "uly" => "BTC-USD",
            //                 "vega" => "0.0020207455080045846",
            //                 "vegaBS" => "74.44022302387287",
            //                 "volLv" => "0.5948549730405797"
            //             ),
            //         ),
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_all_greeks($data, $symbols);
        }) ();
    }

    public function parse_greeks(array $greeks, ?array $market = null): array {
        //
        //     {
        //         "askVol" => "0",
        //         "bidVol" => "0",
        //         "delta" => "0.5105464486882039",
        //         "deltaBS" => "0.7325502184143025",
        //         "fwdPx" => "37675.80158694987186",
        //         "gamma" => "-0.13183515090501083",
        //         "gammaBS" => "0.000024139685826358558",
        //         "instId" => "BTC-USD-240329-32000-C",
        //         "instType" => "OPTION",
        //         "lever" => "4.504428015946619",
        //         "markVol" => "0.5916253554539876",
        //         "realVol" => "0",
        //         "theta" => "-0.0004202992014012855",
        //         "thetaBS" => "-18.52354631567909",
        //         "ts" => "1699586421976",
        //         "uly" => "BTC-USD",
        //         "vega" => "0.0020207455080045846",
        //         "vegaBS" => "74.44022302387287",
        //         "volLv" => "0.5948549730405797"
        //     }
        //
        $timestamp = $this->safe_integer($greeks, 'ts');
        $marketId = $this->safe_string($greeks, 'instId');
        $symbol = $this->safe_symbol($marketId, $market);
        return array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'delta' => $this->safe_number($greeks, 'delta'),
            'gamma' => $this->safe_number($greeks, 'gamma'),
            'theta' => $this->safe_number($greeks, 'theta'),
            'vega' => $this->safe_number($greeks, 'vega'),
            'rho' => null,
            'bidSize' => null,
            'askSize' => null,
            'bidImpliedVolatility' => $this->safe_number($greeks, 'bidVol'),
            'askImpliedVolatility' => $this->safe_number($greeks, 'askVol'),
            'markImpliedVolatility' => $this->safe_number($greeks, 'markVol'),
            'bidPrice' => null,
            'askPrice' => null,
            'markPrice' => null,
            'lastPrice' => null,
            'underlyingPrice' => null,
            'info' => $greeks,
        );
    }

    public function close_position(string $symbol, ?string $side = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $side, $params) {
            /**
             * closes open positions for a $market
             *
             * @see https://www.okx.com/docs-v5/en/#$order-book-trading-trade-post-close-positions
             *
             * @param {string} $symbol Unified CCXT $market $symbol
             * @param {string} [$side] 'buy' or 'sell', leave in net mode
             * @param {array} [$params] extra parameters specific to the okx api endpoint
             * @param {string} [$params->clientOrderId] a unique identifier for the $order
             * @param {string} [$params->marginMode] 'cross' or 'isolated', default is 'cross;
             * @param {string} [$params->code] *required in the case of closing cross MARGIN position for Single-$currency margin* margin $currency
             *
             * EXCHANGE SPECIFIC PARAMETERS
             * @param {boolean} [$params->autoCxl] whether any pending orders for closing out needs to be automatically canceled when close position via a $market $order-> false or true, the default is false
             * @param {string} [$params->tag] $order tag a combination of case-sensitive alphanumerics, all numbers, or all letters of up to 16 characters
             * @return {array[]} ~@link https://docs.ccxt.com/#/?id=position-structure A list of position structures~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $clientOrderId = $this->safe_string($params, 'clientOrderId');
            $code = $this->safe_string($params, 'code');
            $marginMode = null;
            list($marginMode, $params) = $this->handle_margin_mode_and_params('closePosition', $params, 'cross');
            $request = array(
                'instId' => $market['id'],
                'mgnMode' => $marginMode,
            );
            if ($side !== null) {
                if (($side === 'buy')) {
                    $request['posSide'] = 'long';
                } elseif ($side === 'sell') {
                    $request['posSide'] = 'short';
                } else {
                    $request['posSide'] = $side;
                }
            }
            if ($clientOrderId !== null) {
                $request['clOrdId'] = $clientOrderId;
            }
            if ($code !== null) {
                $currency = $this->currency($code);
                $request['ccy'] = $currency['id'];
            }
            $response = Async\await($this->privatePostTradeClosePosition ($this->extend($request, $params)));
            //
            //    {
            //        "code" => "1",
            //        "data" => array(
            //            {
            //                "clOrdId":"e847386590ce4dBCe903bbc394dc88bf",
            //                "ordId":"",
            //                "sCode":"51000",
            //                "sMsg":"Parameter posSide error ",
            //                "tag":"e847386590ce4dBC"
            //            }
            //        ),
            //        "inTime" => "1701877077101064",
            //        "msg" => "All operations failed",
            //        "outTime" => "1701877077102579"
            //    }
            //
            $data = $this->safe_list($response, 'data', array());
            $order = $this->safe_dict($data, 0);
            return $this->parse_order($order, $market);
        }) ();
    }

    public function fetch_option(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetches option data that is commonly found in an option $chain
             *
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-$market-data-get-ticker
             *
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=option-$chain-structure option $chain structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'instId' => $market['id'],
            );
            $response = Async\await($this->publicGetMarketTicker ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "msg" => "",
            //         "data" => array(
            //             {
            //                 "instType" => "OPTION",
            //                 "instId" => "BTC-USD-241227-60000-P",
            //                 "last" => "",
            //                 "lastSz" => "0",
            //                 "askPx" => "",
            //                 "askSz" => "0",
            //                 "bidPx" => "",
            //                 "bidSz" => "0",
            //                 "open24h" => "",
            //                 "high24h" => "",
            //                 "low24h" => "",
            //                 "volCcy24h" => "0",
            //                 "vol24h" => "0",
            //                 "ts" => "1711176035035",
            //                 "sodUtc0" => "",
            //                 "sodUtc8" => ""
            //             }
            //         )
            //     }
            //
            $result = $this->safe_list($response, 'data', array());
            $chain = $this->safe_dict($result, 0, array());
            return $this->parse_option($chain, null, $market);
        }) ();
    }

    public function fetch_option_chain(string $code, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $params) {
            /**
             * fetches data for an underlying asset that is commonly found in an option chain
             *
             * @see https://www.okx.com/docs-v5/en/#order-book-trading-market-data-get-tickers
             *
             * @param {string} $code base $currency to fetch an option chain for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->uly] the underlying asset, can be obtained from fetchUnderlyingAssets ()
             * @return {array} a list of ~@link https://docs.ccxt.com/#/?id=option-chain-structure option chain structures~
             */
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            $request = array(
                'uly' => $currency['code'] . '-USD',
                'instType' => 'OPTION',
            );
            $response = Async\await($this->publicGetMarketTickers ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "msg" => "",
            //         "data" => array(
            //             array(
            //                 "instType" => "OPTION",
            //                 "instId" => "BTC-USD-240323-52000-C",
            //                 "last" => "",
            //                 "lastSz" => "0",
            //                 "askPx" => "",
            //                 "askSz" => "0",
            //                 "bidPx" => "",
            //                 "bidSz" => "0",
            //                 "open24h" => "",
            //                 "high24h" => "",
            //                 "low24h" => "",
            //                 "volCcy24h" => "0",
            //                 "vol24h" => "0",
            //                 "ts" => "1711176207008",
            //                 "sodUtc0" => "",
            //                 "sodUtc8" => ""
            //             ),
            //         )
            //     }
            //
            $result = $this->safe_list($response, 'data', array());
            return $this->parse_option_chain($result, null, 'instId');
        }) ();
    }

    public function parse_option(array $chain, ?array $currency = null, ?array $market = null): Option {
        //
        //     {
        //         "instType" => "OPTION",
        //         "instId" => "BTC-USD-241227-60000-P",
        //         "last" => "",
        //         "lastSz" => "0",
        //         "askPx" => "",
        //         "askSz" => "0",
        //         "bidPx" => "",
        //         "bidSz" => "0",
        //         "open24h" => "",
        //         "high24h" => "",
        //         "low24h" => "",
        //         "volCcy24h" => "0",
        //         "vol24h" => "0",
        //         "ts" => "1711176035035",
        //         "sodUtc0" => "",
        //         "sodUtc8" => ""
        //     }
        //
        $marketId = $this->safe_string($chain, 'instId');
        $market = $this->safe_market($marketId, $market);
        $timestamp = $this->safe_integer($chain, 'ts');
        return array(
            'info' => $chain,
            'currency' => null,
            'symbol' => $market['symbol'],
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'impliedVolatility' => null,
            'openInterest' => null,
            'bidPrice' => $this->safe_number($chain, 'bidPx'),
            'askPrice' => $this->safe_number($chain, 'askPx'),
            'midPrice' => null,
            'markPrice' => null,
            'lastPrice' => $this->safe_number($chain, 'last'),
            'underlyingPrice' => null,
            'change' => null,
            'percentage' => null,
            'baseVolume' => $this->safe_number($chain, 'volCcy24h'),
            'quoteVolume' => null,
        );
    }

    public function fetch_convert_quote(string $fromCode, string $toCode, ?float $amount = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($fromCode, $toCode, $amount, $params) {
            /**
             * fetch a quote for converting from one currency to another
             *
             * @see https://www.okx.com/docs-v5/en/#funding-account-rest-api-estimate-quote
             *
             * @param {string} $fromCode the currency that you want to sell and convert from
             * @param {string} $toCode the currency that you want to buy and convert into
             * @param {float} [$amount] how much you want to trade in units of the from currency
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=conversion-structure conversion structure~
             */
            Async\await($this->load_markets());
            $request = array(
                'baseCcy' => strtoupper($fromCode),
                'quoteCcy' => strtoupper($toCode),
                'rfqSzCcy' => strtoupper($fromCode),
                'rfqSz' => $this->number_to_string($amount),
                'side' => 'sell',
            );
            $response = Async\await($this->privatePostAssetConvertEstimateQuote ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "baseCcy" => "ETH",
            //                 "baseSz" => "0.01023052",
            //                 "clQReqId" => "",
            //                 "cnvtPx" => "2932.40104429",
            //                 "origRfqSz" => "30",
            //                 "quoteCcy" => "USDT",
            //                 "quoteId" => "quoterETH-USDT16461885104612381",
            //                 "quoteSz" => "30",
            //                 "quoteTime" => "1646188510461",
            //                 "rfqSz" => "30",
            //                 "rfqSzCcy" => "USDT",
            //                 "side" => "buy",
            //                 "ttlMs" => "10000"
            //             }
            //         ),
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $result = $this->safe_dict($data, 0, array());
            $fromCurrencyId = $this->safe_string($result, 'baseCcy', $fromCode);
            $fromCurrency = $this->currency($fromCurrencyId);
            $toCurrencyId = $this->safe_string($result, 'quoteCcy', $toCode);
            $toCurrency = $this->currency($toCurrencyId);
            return $this->parse_conversion($result, $fromCurrency, $toCurrency);
        }) ();
    }

    public function create_convert_trade(string $id, string $fromCode, string $toCode, ?float $amount = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($id, $fromCode, $toCode, $amount, $params) {
            /**
             * convert from one currency to another
             *
             * @see https://www.okx.com/docs-v5/en/#funding-account-rest-api-convert-trade
             *
             * @param {string} $id the $id of the trade that you want to make
             * @param {string} $fromCode the currency that you want to sell and convert from
             * @param {string} $toCode the currency that you want to buy and convert into
             * @param {float} [$amount] how much you want to trade in units of the from currency
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?$id=conversion-structure conversion structure~
             */
            Async\await($this->load_markets());
            $request = array(
                'quoteId' => $id,
                'baseCcy' => $fromCode,
                'quoteCcy' => $toCode,
                'szCcy' => $fromCode,
                'sz' => $this->number_to_string($amount),
                'side' => 'sell',
            );
            $response = Async\await($this->privatePostAssetConvertTrade ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "baseCcy" => "ETH",
            //                 "clTReqId" => "",
            //                 "fillBaseSz" => "0.01023052",
            //                 "fillPx" => "2932.40104429",
            //                 "fillQuoteSz" => "30",
            //                 "instId" => "ETH-USDT",
            //                 "quoteCcy" => "USDT",
            //                 "quoteId" => "quoterETH-USDT16461885104612381",
            //                 "side" => "buy",
            //                 "state" => "fullyFilled",
            //                 "tradeId" => "trader16461885203381437",
            //                 "ts" => "1646188520338"
            //             }
            //         ),
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $result = $this->safe_dict($data, 0, array());
            $fromCurrencyId = $this->safe_string($result, 'baseCcy', $fromCode);
            $fromCurrency = $this->currency($fromCurrencyId);
            $toCurrencyId = $this->safe_string($result, 'quoteCcy', $toCode);
            $toCurrency = $this->currency($toCurrencyId);
            return $this->parse_conversion($result, $fromCurrency, $toCurrency);
        }) ();
    }

    public function fetch_convert_trade(string $id, ?string $code = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($id, $code, $params) {
            /**
             * fetch the $data for a conversion trade
             *
             * @see https://www.okx.com/docs-v5/en/#funding-account-rest-api-get-convert-history
             *
             * @param {string} $id the $id of the trade that you want to fetch
             * @param {string} [$code] the unified currency $code of the conversion trade
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?$id=conversion-structure conversion structure~
             */
            Async\await($this->load_markets());
            $request = array(
                'clTReqId' => $id,
            );
            $response = Async\await($this->privateGetAssetConvertHistory ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "clTReqId" => "",
            //                 "instId" => "ETH-USDT",
            //                 "side" => "buy",
            //                 "fillPx" => "2932.401044",
            //                 "baseCcy" => "ETH",
            //                 "quoteCcy" => "USDT",
            //                 "fillBaseSz" => "0.01023052",
            //                 "state" => "fullyFilled",
            //                 "tradeId" => "trader16461885203381437",
            //                 "fillQuoteSz" => "30",
            //                 "ts" => "1646188520000"
            //             }
            //         ),
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $result = $this->safe_dict($data, 0, array());
            $fromCurrencyId = $this->safe_string($result, 'baseCcy');
            $toCurrencyId = $this->safe_string($result, 'quoteCcy');
            $fromCurrency = null;
            $toCurrency = null;
            if ($fromCurrencyId !== null) {
                $fromCurrency = $this->currency($fromCurrencyId);
            }
            if ($toCurrencyId !== null) {
                $toCurrency = $this->currency($toCurrencyId);
            }
            return $this->parse_conversion($result, $fromCurrency, $toCurrency);
        }) ();
    }

    public function fetch_convert_trade_history(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch the users history of conversion trades
             *
             * @see https://www.okx.com/docs-v5/en/#funding-account-rest-api-get-convert-history
             *
             * @param {string} [$code] the unified currency $code
             * @param {int} [$since] the earliest time in ms to fetch conversions for
             * @param {int} [$limit] the maximum number of conversion structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] timestamp in ms of the latest conversion to fetch
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=conversion-structure conversion structures~
             */
            Async\await($this->load_markets());
            $request = array();
            list($request, $params) = $this->handle_until_option('after', $request, $params);
            if ($since !== null) {
                $request['before'] = $since;
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $response = Async\await($this->privateGetAssetConvertHistory ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             {
            //                 "clTReqId" => "",
            //                 "instId" => "ETH-USDT",
            //                 "side" => "buy",
            //                 "fillPx" => "2932.401044",
            //                 "baseCcy" => "ETH",
            //                 "quoteCcy" => "USDT",
            //                 "fillBaseSz" => "0.01023052",
            //                 "state" => "fullyFilled",
            //                 "tradeId" => "trader16461885203381437",
            //                 "fillQuoteSz" => "30",
            //                 "ts" => "1646188520000"
            //             }
            //         ),
            //         "msg" => ""
            //     }
            //
            $rows = $this->safe_list($response, 'data', array());
            return $this->parse_conversions($rows, $code, 'baseCcy', 'quoteCcy', $since, $limit);
        }) ();
    }

    public function parse_conversion(array $conversion, ?array $fromCurrency = null, ?array $toCurrency = null): array {
        //
        // fetchConvertQuote
        //
        //     {
        //         "baseCcy" => "ETH",
        //         "baseSz" => "0.01023052",
        //         "clQReqId" => "",
        //         "cnvtPx" => "2932.40104429",
        //         "origRfqSz" => "30",
        //         "quoteCcy" => "USDT",
        //         "quoteId" => "quoterETH-USDT16461885104612381",
        //         "quoteSz" => "30",
        //         "quoteTime" => "1646188510461",
        //         "rfqSz" => "30",
        //         "rfqSzCcy" => "USDT",
        //         "side" => "buy",
        //         "ttlMs" => "10000"
        //     }
        //
        // createConvertTrade
        //
        //     {
        //         "baseCcy" => "ETH",
        //         "clTReqId" => "",
        //         "fillBaseSz" => "0.01023052",
        //         "fillPx" => "2932.40104429",
        //         "fillQuoteSz" => "30",
        //         "instId" => "ETH-USDT",
        //         "quoteCcy" => "USDT",
        //         "quoteId" => "quoterETH-USDT16461885104612381",
        //         "side" => "buy",
        //         "state" => "fullyFilled",
        //         "tradeId" => "trader16461885203381437",
        //         "ts" => "1646188520338"
        //     }
        //
        // fetchConvertTrade, fetchConvertTradeHistory
        //
        //     {
        //         "clTReqId" => "",
        //         "instId" => "ETH-USDT",
        //         "side" => "buy",
        //         "fillPx" => "2932.401044",
        //         "baseCcy" => "ETH",
        //         "quoteCcy" => "USDT",
        //         "fillBaseSz" => "0.01023052",
        //         "state" => "fullyFilled",
        //         "tradeId" => "trader16461885203381437",
        //         "fillQuoteSz" => "30",
        //         "ts" => "1646188520000"
        //     }
        //
        $timestamp = $this->safe_integer_2($conversion, 'quoteTime', 'ts');
        $fromCoin = $this->safe_string($conversion, 'baseCcy');
        $fromCode = $this->safe_currency_code($fromCoin, $fromCurrency);
        $to = $this->safe_string($conversion, 'quoteCcy');
        $toCode = $this->safe_currency_code($to, $toCurrency);
        return array(
            'info' => $conversion,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'id' => $this->safe_string_n($conversion, array( 'clQReqId', 'tradeId', 'quoteId' )),
            'fromCurrency' => $fromCode,
            'fromAmount' => $this->safe_number_2($conversion, 'baseSz', 'fillBaseSz'),
            'toCurrency' => $toCode,
            'toAmount' => $this->safe_number_2($conversion, 'quoteSz', 'fillQuoteSz'),
            'price' => $this->safe_number_2($conversion, 'cnvtPx', 'fillPx'),
            'fee' => null,
        );
    }

    public function fetch_convert_currencies($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetches all available currencies that can be converted
             *
             * @see https://www.okx.com/docs-v5/en/#funding-account-rest-api-get-convert-currencies
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an associative dictionary of currencies
             */
            Async\await($this->load_markets());
            $response = Async\await($this->privateGetAssetConvertCurrencies ($params));
            //
            //     {
            //         "code" => "0",
            //         "data" => array(
            //             array(
            //                 "ccy" => "BTC",
            //                 "max" => "",
            //                 "min" => ""
            //             ),
            //         ),
            //         "msg" => ""
            //     }
            //
            $result = array();
            $data = $this->safe_list($response, 'data', array());
            for ($i = 0; $i < count($data); $i++) {
                $entry = $data[$i];
                $id = $this->safe_string($entry, 'ccy');
                $code = $this->safe_currency_code($id);
                $result[$code] = array(
                    'info' => $entry,
                    'id' => $id,
                    'code' => $code,
                    'networks' => null,
                    'type' => null,
                    'name' => null,
                    'active' => null,
                    'deposit' => null,
                    'withdraw' => null,
                    'fee' => null,
                    'precision' => null,
                    'limits' => array(
                        'amount' => array(
                            'min' => $this->safe_number($entry, 'min'),
                            'max' => $this->safe_number($entry, 'max'),
                        ),
                        'withdraw' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'deposit' => array(
                            'min' => null,
                            'max' => null,
                        ),
                    ),
                    'created' => null,
                );
            }
            return $result;
        }) ();
    }

    public function handle_errors(int $httpCode, string $reason, string $url, string $method, array $headers, string $body, $response, $requestHeaders, $requestBody) {
        if (!$response) {
            return null; // fallback to default $error handler
        }
        //
        //    {
        //        "code" => "1",
        //        "data" => array(
        //            array(
        //                "clOrdId" => "",
        //                "ordId" => "",
        //                "sCode" => "51119",
        //                "sMsg" => "Order placement failed due to insufficient balance. ",
        //                "tag" => ""
        //            }
        //        ),
        //        "msg" => ""
        //    ),
        //    {
        //        "code" => "58001",
        //        "data" => array(),
        //        "msg" => "Incorrect trade password"
        //    }
        //
        $code = $this->safe_string($response, 'code');
        if (($code !== '0') && ($code !== '2')) { // 2 means that bulk operation partially succeeded
            $feedback = $this->id . ' ' . $body;
            $data = $this->safe_list($response, 'data', array());
            for ($i = 0; $i < count($data); $i++) {
                $error = $data[$i];
                $errorCode = $this->safe_string($error, 'sCode');
                $message = $this->safe_string($error, 'sMsg');
                $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorCode, $feedback);
                $this->throw_broadly_matched_exception($this->exceptions['broad'], $message, $feedback);
            }
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $code, $feedback);
            throw new ExchangeError($feedback); // unknown $message
        }
        return null;
    }

    public function fetch_margin_adjustment_history(?string $symbol = null, ?string $type = null, ?float $since = null, ?float $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $type, $since, $limit, $params) {
            /**
             * fetches the history of margin added or reduced from contract isolated positions
             *
             * @see https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-bills-details-last-7-days
             * @see https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-bills-details-last-3-months
             *
             * @param {string} [$symbol] not used by okx fetchMarginAdjustmentHistory
             * @param {string} [$type] "add" or "reduce"
             * @param {int} [$since] the earliest time in ms to fetch margin adjustment history for
             * @param {int} [$limit] the maximum number of entries to retrieve
             * @param {array} $params extra parameters specific to the exchange api endpoint
             * @param {boolean} [$params->auto] true if fetching $auto margin increases
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=margin-loan-structure margin structures~
             */
            Async\await($this->load_markets());
            $auto = $this->safe_bool($params, 'auto');
            if ($type === null) {
                throw new ArgumentsRequired($this->id . ' fetchMarginAdjustmentHistory () requires a $type argument');
            }
            $isAdd = $type === 'add';
            $subType = $isAdd ? '160' : '161';
            if ($auto) {
                if ($isAdd) {
                    $subType = '162';
                } else {
                    throw new BadRequest($this->id . ' cannot fetch margin adjustments for $type ' . $type);
                }
            }
            $request = array(
                'subType' => $subType,
                'mgnMode' => 'isolated',
            );
            $until = $this->safe_integer($params, 'until');
            $params = $this->omit($params, 'until');
            if ($since !== null) {
                $request['startTime'] = $since;
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            if ($until !== null) {
                $request['endTime'] = $until;
            }
            $response = null;
            $now = $this->milliseconds();
            $oneWeekAgo = $now - 604800000;
            $threeMonthsAgo = $now - 7776000000;
            if (($since === null) || ($since > $oneWeekAgo)) {
                $response = Async\await($this->privateGetAccountBills ($this->extend($request, $params)));
            } elseif ($since > $threeMonthsAgo) {
                $response = Async\await($this->privateGetAccountBillsArchive ($this->extend($request, $params)));
            } else {
                throw new BadRequest($this->id . ' fetchMarginAdjustmentHistory () cannot fetch margin adjustments older than 3 months');
            }
            //
            //    {
            //        code => '0',
            //        $data => array(
            //            {
            //                bal => '67621.4325135010619812',
            //                balChg => '-10.0000000000000000',
            //                billId => '691293628710342659',
            //                ccy => 'USDT',
            //                clOrdId => '',
            //                execType => '',
            //                fee => '0',
            //                fillFwdPx => '',
            //                fillIdxPx => '',
            //                fillMarkPx => '',
            //                fillMarkVol => '',
            //                fillPxUsd => '',
            //                fillPxVol => '',
            //                fillTime => '1711089244850',
            //                from => '',
            //                instId => 'XRP-USDT-SWAP',
            //                instType => 'SWAP',
            //                interest => '0',
            //                mgnMode => 'isolated',
            //                notes => '',
            //                ordId => '',
            //                pnl => '0',
            //                posBal => '73.12',
            //                posBalChg => '10.00',
            //                px => '',
            //                $subType => '160',
            //                sz => '10',
            //                tag => '',
            //                to => '',
            //                tradeId => '0',
            //                ts => '1711089244699',
            //                $type => '6'
            //            }
            //        ),
            //        msg => ''
            //    }
            //
            $data = $this->safe_list($response, 'data');
            $modifications = $this->parse_margin_modifications($data);
            return $this->filter_by_symbol_since_limit($modifications, $symbol, $since, $limit);
        }) ();
    }

    public function fetch_positions_history(?array $symbols = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $since, $limit, $params) {
            /**
             * fetches historical $positions
             *
             * @see https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-$positions-history
             *
             * @param {string} [$symbols] unified $market $symbols
             * @param {int} [$since] timestamp in ms of the earliest position to fetch
             * @param {int} [$limit] the maximum amount of records to fetch, default=100, max=100
             * @param {array} $params extra parameters specific to the exchange api endpoint
             * @param {string} [$params->marginMode] "cross" or "isolated"
             *
             * EXCHANGE SPECIFIC PARAMETERS
             * @param {string} [$params->instType] margin, swap, futures or option
             * @param {string} [$params->type] the type of latest close position 1 => close position partially, 2：close all, 3：liquidation, 4：partial liquidation; 5：adl, is it is the latest type if there are several types for the same position
             * @param {string} [$params->posId] position id, there is attribute expiration, the posid will be expired if it is more than 30 days after the last full close position, then position will use new posid
             * @param {string} [$params->before] timestamp in ms of the earliest position to fetch based on the last update time of the position
             * @param {string} [$params->after] timestamp in ms of the latest position to fetch based on the last update time of the position
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=position-structure position structures~
             */
            Async\await($this->load_markets());
            $marginMode = $this->safe_string($params, 'marginMode');
            $instType = $this->safe_string_upper($params, 'instType');
            $params = $this->omit($params, array( 'until', 'marginMode', 'instType' ));
            if ($limit === null) {
                $limit = 100;
            }
            $request = array(
                'limit' => $limit,
            );
            if ($symbols !== null) {
                $symbolsLength = count($symbols);
                if ($symbolsLength === 1) {
                    $market = $this->market($symbols[0]);
                    $request['instId'] = $market['id'];
                }
            }
            if ($marginMode !== null) {
                $request['mgnMode'] = $marginMode;
            }
            if ($instType !== null) {
                $request['instType'] = $instType;
            }
            $response = Async\await($this->privateGetAccountPositionsHistory ($this->extend($request, $params)));
            //
            //    {
            //        code => '0',
            //        $data => array(
            //            array(
            //                cTime => '1708735940395',
            //                ccy => 'USDT',
            //                closeAvgPx => '0.6330444444444444',
            //                closeTotalPos => '27',
            //                direction => 'long',
            //                fee => '-1.69566',
            //                fundingFee => '-11.870404179341788',
            //                instId => 'XRP-USDT-SWAP',
            //                $instType => 'SWAP',
            //                lever => '3.0',
            //                liqPenalty => '0',
            //                mgnMode => 'cross',
            //                openAvgPx => '0.623',
            //                openMaxPos => '15',
            //                pnl => '27.11999999999988',
            //                pnlRatio => '0.0241732402722634',
            //                posId => '681423155054862336',
            //                realizedPnl => '13.553935820658092',
            //                triggerPx => '',
            //                type => '2',
            //                uTime => '1711088748170',
            //                uly => 'XRP-USDT'
            //            ),
            //            ...
            //        ),
            //        msg => ''
            //    }
            //
            $data = $this->safe_list($response, 'data');
            $positions = $this->parse_positions($data, $symbols, $params);
            return $this->filter_by_since_limit($positions, $since, $limit);
        }) ();
    }

    public function fetch_long_short_ratio_history(?string $symbol = null, ?string $timeframe = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $timeframe, $since, $limit, $params) {
            /**
             * fetches the long short ratio history for a unified $market $symbol
             *
             * @see https://www.okx.com/docs-v5/en/#trading-statistics-rest-api-get-contract-long-short-ratio
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the long short ratio for
             * @param {string} [$timeframe] the period for the ratio
             * @param {int} [$since] the earliest time in ms to fetch ratios for
             * @param {int} [$limit] the maximum number of long short ratio structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] timestamp in ms of the latest ratio to fetch
             * @return {array[]} an array of ~@link https://docs.ccxt.com/#/?id=long-short-ratio-structure long short ratio structures~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'instId' => $market['id'],
            );
            $until = $this->safe_string_2($params, 'until', 'end');
            $params = $this->omit($params, 'until');
            if ($until !== null) {
                $request['end'] = $until;
            }
            if ($timeframe !== null) {
                $request['period'] = $timeframe;
            }
            if ($since !== null) {
                $request['begin'] = $since;
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $response = Async\await($this->publicGetRubikStatContractsLongShortAccountRatioContract ($this->extend($request, $params)));
            //
            //     {
            //         "code" => "0",
            //         "data" => [
            //             ["1729323600000", "0.9398602814619824"],
            //             ["1729323300000", "0.9398602814619824"],
            //             ["1729323000000", "0.9398602814619824"],
            //         ],
            //         "msg" => ""
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $result = array();
            for ($i = 0; $i < count($data); $i++) {
                $entry = $data[$i];
                $result[] = array(
                    'timestamp' => $this->safe_string($entry, 0),
                    'longShortRatio' => $this->safe_string($entry, 1),
                );
            }
            return $this->parse_long_short_ratio_history($result, $market);
        }) ();
    }

    public function parse_long_short_ratio(array $info, ?array $market = null): array {
        $timestamp = $this->safe_integer($info, 'timestamp');
        $symbol = null;
        if ($market !== null) {
            $symbol = $market['symbol'];
        }
        return array(
            'info' => $info,
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'timeframe' => null,
            'longShortRatio' => $this->safe_number($info, 'longShortRatio'),
        );
    }
}
