<?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\apex as Exchange;
use ccxt\ExchangeError;
use ccxt\ArgumentsRequired;
use ccxt\Precise;
use \React\Async;
use \React\Promise\PromiseInterface;

class apex extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'apex',
            'name' => 'Apex',
            'countries' => array(),
            'version' => 'v3',
            'rateLimit' => 20, // 600 requests per minute, 10 request per second
            'certified' => false,
            'pro' => true,
            'dex' => true,
            'has' => array(
                'CORS' => null,
                'spot' => false,
                'margin' => false,
                'swap' => true,
                'future' => false,
                'option' => false,
                'addMargin' => false,
                'borrowCrossMargin' => false,
                'borrowIsolatedMargin' => false,
                'cancelAllOrders' => true,
                'cancelAllOrdersAfter' => false,
                'cancelOrder' => true,
                'cancelOrders' => false,
                'cancelOrdersForSymbols' => false,
                'closeAllPositions' => false,
                'closePosition' => false,
                'createMarketBuyOrderWithCost' => false,
                'createMarketOrderWithCost' => false,
                'createMarketSellOrderWithCost' => false,
                'createOrder' => true,
                'createOrders' => false,
                'createPostOnlyOrder' => true,
                'createReduceOnlyOrder' => true,
                'createStopOrder' => true,
                'createTriggerOrder' => true,
                'editOrder' => false,
                'fetchAccounts' => true,
                'fetchBalance' => true,
                'fetchBorrowInterest' => false,
                'fetchBorrowRateHistories' => false,
                'fetchBorrowRateHistory' => false,
                'fetchCanceledAndClosedOrders' => false,
                'fetchCanceledOrders' => false,
                'fetchClosedOrders' => false,
                'fetchCrossBorrowRate' => false,
                'fetchCrossBorrowRates' => false,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => false,
                'fetchDepositAddresses' => false,
                'fetchDeposits' => false,
                'fetchDepositWithdrawFee' => false,
                'fetchDepositWithdrawFees' => false,
                'fetchFundingHistory' => true,
                'fetchFundingRate' => false,
                'fetchFundingRateHistory' => true,
                'fetchFundingRates' => false,
                'fetchIndexOHLCV' => false,
                'fetchIsolatedBorrowRate' => false,
                'fetchIsolatedBorrowRates' => false,
                'fetchLedger' => false,
                'fetchLeverage' => false,
                'fetchLeverageTiers' => false,
                'fetchLiquidations' => false,
                'fetchMarginMode' => false,
                'fetchMarketLeverageTiers' => false,
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => false,
                'fetchMyLiquidations' => false,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenInterest' => true,
                'fetchOpenInterestHistory' => false,
                'fetchOpenInterests' => false,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => true,
                'fetchOrderTrades' => true,
                'fetchPosition' => false,
                'fetchPositionMode' => false,
                'fetchPositions' => true,
                'fetchPositionsRisk' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => false,
                'fetchTradingFees' => false,
                'fetchTransfer' => true,
                'fetchTransfers' => true,
                'fetchWithdrawal' => false,
                'fetchWithdrawals' => false,
                'reduceMargin' => false,
                'repayCrossMargin' => false,
                'repayIsolatedMargin' => false,
                'sandbox' => true,
                'setLeverage' => true,
                'setMarginMode' => false,
                'setPositionMode' => false,
                'transfer' => false,
                'withdraw' => false,
            ),
            'timeframes' => array(
                '1m' => '1',
                '5m' => '5',
                '15m' => '15',
                '30m' => '30',
                '1h' => '60',
                '2h' => '120',
                '4h' => '240',
                '6h' => '360',
                '12h' => '720',
                '1d' => 'D',
                '1w' => 'W',
                '1M' => 'M',
            ),
            'hostname' => 'omni.apex.exchange',
            'urls' => array(
                'logo' => 'https://github.com/user-attachments/assets/fef8f2f7-4265-46aa-965e-33a91881cb00',
                'api' => array(
                    'public' => 'https://{hostname}/api',
                    'private' => 'https://{hostname}/api',
                ),
                'test' => array(
                    'public' => 'https://testnet.omni.apex.exchange/api',
                    'private' => 'https://testnet.omni.apex.exchange/api',
                ),
                'www' => 'https://apex.exchange/',
                'doc' => 'https://api-docs.pro.apex.exchange',
                'fees' => 'https://apex-pro.gitbook.io/apex-pro/apex-omni-live-now/trading-perpetual-contracts/trading-fees',
                'referral' => 'https://omni.apex.exchange/trade',
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'v3/symbols' => 1,
                        'v3/history-funding' => 1,
                        'v3/ticker' => 1,
                        'v3/klines' => 1,
                        'v3/trades' => 1,
                        'v3/depth' => 1,
                        'v3/time' => 1,
                        'v3/data/all-ticker-info' => 1,
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'v3/account' => 1,
                        'v3/account-balance' => 1,
                        'v3/fills' => 1,
                        'v3/order-fills' => 1,
                        'v3/order' => 1,
                        'v3/history-orders' => 1,
                        'v3/order-by-client-order-id' => 1,
                        'v3/funding' => 1,
                        'v3/historical-pnl' => 1,
                        'v3/open-orders' => 1,
                        'v3/transfers' => 1,
                        'v3/transfer' => 1,
                    ),
                    'post' => array(
                        'v3/delete-open-orders' => 1,
                        'v3/delete-client-order-id' => 1,
                        'v3/delete-order' => 1,
                        'v3/order' => 1,
                        'v3/set-initial-margin-rate' => 1,
                        'v3/transfer-out' => 1,
                        'v3/contract-transfer-out' => 1,
                    ),
                ),
            ),
            'httpExceptions' => array(
                '403' => '\\ccxt\\RateLimitExceeded', // Forbidden -- You request too many times
            ),
            'exceptions' => array(
                // Uncodumented explanation of error strings:
                // - oc_diff => order cost needed to place this order
                // - new_oc => total order cost of open orders including the order you are trying to open
                // - ob => order balance - the total cost of current open orders
                // - ab => available balance
                'exact' => array(
                    '20006' => 'apikey sign error', // apikey sign error
                    '20016' => 'request para error', // apikey sign error
                    '10001' => '\\ccxt\\BadRequest',
                ),
                'broad' => array(
                    'ORDER_PRICE_MUST_GREETER_ZERO' => '\\ccxt\\InvalidOrder',
                    'ORDER_POSSIBLE_LEAD_TO_ACCOUNT_LIQUIDATED' => '\\ccxt\\InvalidOrder',
                    'ORDER_WITH_THIS_PRICE_CANNOT_REDUCE_POSITION_ONLY' => '\\ccxt\\InvalidOrder',
                ),
            ),
            'fees' => array(
                'swap' => array(
                    'taker' => $this->parse_number('0.0005'),
                    'maker' => $this->parse_number('0.0002'),
                ),
            ),
            'requiredCredentials' => array(
                'apiKey' => true,
                'secret' => true,
                'walletAddress' => false,
                'privateKey' => false,
                'password' => true,
            ),
            'precisionMode' => TICK_SIZE,
            'commonCurrencies' => array(),
            'options' => array(
                'defaultType' => 'swap',
                'defaultSlippage' => 0.05,
                'brokerId' => '6956',
            ),
            'features' => array(
                'default' => array(
                    'sandbox' => true,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => true,
                        'triggerPriceType' => null,
                        'triggerDirection' => false,
                        'stopLossPrice' => false, // todo
                        'takeProfitPrice' => false, // todo
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => true,
                            'GTD' => true,
                        ),
                        'hedged' => false,
                        'selfTradePrevention' => false,
                        'trailing' => true, // todo unify
                        'leverage' => false,
                        'marketBuyByCost' => false,
                        'marketBuyRequiresPrice' => false,
                        'iceberg' => false,
                    ),
                    'createOrders' => null,
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 500,
                        'daysBack' => 100000,
                        'untilDays' => 100000,
                        'symbolRequired' => false,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => null,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => array(
                        'marginMode' => false,
                        'limit' => 100,
                        'daysBack' => 100000,
                        'untilDays' => 100000,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchClosedOrders' => null,
                    'fetchOHLCV' => array(
                        'limit' => 200,
                    ),
                ),
                'swap' => array(
                    'linear' => array(
                        'extends' => 'default',
                    ),
                    'inverse' => null,
                ),
            ),
        ));
    }

    public function fetch_time($params = array ()) {
        return Async\async(function () use ($params) {
            /**
             * fetches the current integer timestamp in milliseconds from the exchange server
             *
             * @see https://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-system-time-v3
             *
             * @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->publicGetV3Time ($params));
            $data = $this->safe_dict($response, 'data', array());
            //
            // {
            //    "data" => {
            //    "time" => 1738837534454
            //     }
            // }
            return $this->safe_integer($data, 'time');
        }) ();
    }

    public function parse_balance($response): array {
        //
        // {
        //     "totalEquityValue" => "100.000000",
        //     "availableBalance" => "100.000000",
        //     "initialMargin" => "100.000000",
        //     "maintenanceMargin" => "100.000000",
        //     "symbolToOraclePrice" => {
        //     "BTC-USDC" => {
        //         "oraclePrice" => "20000",
        //             "createdTime" => 124566
        //     }
        // }
        // }
        //
        $timestamp = $this->milliseconds();
        $result = array(
            'info' => $response,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
        );
        $code = 'USDT';
        $account = $this->account();
        $account['free'] = $this->safe_string($response, 'availableBalance');
        $account['total'] = $this->safe_string($response, 'totalEquityValue');
        $result[$code] = $account;
        return $this->safe_balance($result);
    }

    public function fetch_balance($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * query for account info
             *
             * @see https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-retrieve-user-account-balance
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
             */
            Async\await($this->load_markets());
            $response = Async\await($this->privateGetV3AccountBalance ($params));
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_balance($data);
        }) ();
    }

    public function parse_account(array $account): array {
        $accountId = $this->safe_string($account, 'id', '0');
        return array(
            'id' => $accountId,
            'type' => null,
            'code' => null,
            'info' => $account,
        );
    }

    public function fetch_account($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://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-retrieve-user-account-$data
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
             */
            Async\await($this->load_markets());
            $response = Async\await($this->privateGetV3Account ($params));
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_account($data);
        }) ();
    }

    public function fetch_currencies($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetches all available currencies on an exchange
             *
             * @see https://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-all-config-$data-v3
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an associative dictionary of currencies
             */
            $response = Async\await($this->publicGetV3Symbols ($params));
            $data = $this->safe_dict($response, 'data', array());
            $spotConfig = $this->safe_dict($data, 'spotConfig', array());
            $multiChain = $this->safe_dict($spotConfig, 'multiChain', array());
            // "spotConfig" => {
            //     "assets" => array(
            //         {
            //             "tokenId" => "141",
            //             "token" => "USDT",
            //             "displayName" => "Tether USD Coin",
            //             "decimals" => 18,
            //             "showStep" => "0.01",
            //             "iconUrl" => "https://static-pro.apex.exchange/chains/chain_tokens/Ethereum/Ethereum_USDT.svg",
            //             "l2WithdrawFee" => "0",
            //             "enableCollateral" => true,
            //             "enableCrossCollateral" => false,
            //             "crossCollateralDiscountRate" => null,
            //             "isGray" => false
            //         }
            //     ),
            // "multiChain" => {
            //  "chains" => array(
            //      {
            //          "chain" => "Arbitrum One",
            //          "chainId" => "9",
            //          "chainType" => "0",
            //          "l1ChainId" => "42161",
            //          "chainIconUrl" => "https://static-pro.apex.exchange/chains/chain_logos/Arbitrum.svg",
            //          "contractAddress" => "0x3169844a120c0f517b4eb4a750c08d8518c8466a",
            //          "swapContractAddress" => "0x9e07b6Aef1bbD9E513fc2Eb8873e311E80B4f855",
            //          "stopDeposit" => false,
            //          "feeLess" => false,
            //          "gasLess" => false,
            //          "gasToken" => "ETH",
            //          "dynamicFee" => true,
            //          "gasTokenDecimals" => 18,
            //          "feeGasLimit" => 300000,
            //          "blockTimeSeconds" => 2,
            //          "rpcUrl" => "https://arb.pro.apex.exchange",
            //          "minSwapUsdtAmount" => "",
            //          "maxSwapUsdtAmount" => "",
            //          "webRpcUrl" => "https://arb.pro.apex.exchange",
            //          "webTxUrl" => "https://arbiscan.io/tx/",
            //          "backupRpcUrl" => "https://arb-mainnet.g.alchemy.com/v2/rGlYUbRHtUav5mfeThCPtsV9GLPt2Xq5",
            //          "txConfirm" => 20,
            //          "withdrawGasFeeLess" => false,
            //          "tokens" => array(
            //              array(
            //                  "decimals" => 6,
            //                  "iconUrl" => "https://static-pro.apex.exchange/chains/chain_tokens/Arbitrum/Arbitrum_USDT.svg",
            //                  "token" => "USDT",
            //                  "tokenAddress" => "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
            //                  "pullOff" => false,
            //                  "withdrawEnable" => true,
            //                  "slippage" => "",
            //                  "isDefaultToken" => false,
            //                  "displayToken" => "USDT",
            //                  "needResetApproval" => true,
            //                  "minFee" => "2",
            //                  "maxFee" => "40",
            //                  "feeRate" => "0.0001",
            //                  "maxWithdraw" => "",
            //                  "minDeposit" => "",
            //                  "minWithdraw" => "",
            //                  "maxFastWithdrawAmount" => "40000",
            //                  "minFastWithdrawAmount" => "1",
            //                  "isGray" => false
            //              ),
            //              {
            //                  "decimals" => 6,
            //                  "iconUrl" => "https://static-pro.apex.exchange/chains/chain_tokens/Arbitrum/Arbitrum_USDC.svg",
            //                  "token" => "USDC",
            //                  "tokenAddress" => "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
            //                  "pullOff" => false,
            //                  "withdrawEnable" => true,
            //                  "slippage" => "",
            //                  "isDefaultToken" => false,
            //                  "displayToken" => "USDC",
            //                  "needResetApproval" => true,
            //                  "minFee" => "2",
            //                  "maxFee" => "20",
            //                  "feeRate" => "0.0001",
            //                  "maxWithdraw" => "",
            //                  "minDeposit" => "",
            //                  "minWithdraw" => "",
            //                  "maxFastWithdrawAmount" => "1",
            //                  "minFastWithdrawAmount" => "1",
            //                  "isGray" => false
            //              }
            //          )
            //        }
            //     )
            // }
            $rows = $this->safe_list($spotConfig, 'assets', array());
            $chains = $this->safe_list($multiChain, 'chains', array());
            $result = array();
            for ($i = 0; $i < count($rows); $i++) {
                $currency = $rows[$i];
                $currencyId = $this->safe_string($currency, 'token');
                $code = $this->safe_currency_code($currencyId);
                $name = $this->safe_string($currency, 'displayName');
                $networks = array();
                for ($j = 0; $j < count($chains); $j++) {
                    $chain = $chains[$j];
                    $tokens = $this->safe_list($chain, 'tokens', array());
                    for ($f = 0; $f < count($tokens); $f++) {
                        $token = $tokens[$f];
                        $tokenName = $this->safe_string($token, 'token');
                        if ($tokenName === $currencyId) {
                            $networkId = $this->safe_string($chain, 'chainId');
                            $networkCode = $this->network_id_to_code($networkId);
                            $networks[$networkCode] = array(
                                'info' => $chain,
                                'id' => $networkId,
                                'network' => $networkCode,
                                'active' => null,
                                'deposit' => !$this->safe_bool($chain, 'depositDisable'),
                                'withdraw' => $this->safe_bool($token, 'withdrawEnable'),
                                'fee' => $this->safe_number($token, 'minFee'),
                                'precision' => $this->parse_number($this->parse_precision($this->safe_string($token, 'decimals'))),
                                'limits' => array(
                                    'withdraw' => array(
                                        'min' => $this->safe_number($token, 'minWithdraw'),
                                        'max' => null,
                                    ),
                                    'deposit' => array(
                                        'min' => $this->safe_number($chain, 'minDeposit'),
                                        'max' => null,
                                    ),
                                ),
                            );
                        }
                    }
                }
                $networkKeys = is_array($networks) ? array_keys($networks) : array();
                $networksLength = count($networkKeys);
                $emptyChains = $networksLength === 0; // non-functional coins
                $valueForEmpty = $emptyChains ? false : null;
                $result[$code] = $this->safe_currency_structure(array(
                    'info' => $currency,
                    'code' => $code,
                    'id' => $currencyId,
                    'type' => 'crypto',
                    'name' => $name,
                    'active' => null,
                    'deposit' => $valueForEmpty,
                    'withdraw' => $valueForEmpty,
                    'fee' => null,
                    'precision' => null,
                    'limits' => array(
                        'amount' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'withdraw' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'deposit' => array(
                            'min' => null,
                            'max' => null,
                        ),
                    ),
                    'networks' => $networks,
                ));
            }
            return $result;
        }) ();
    }

    public function fetch_markets($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * retrieves $data on all markets for apex
             *
             * @see https://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-all-config-$data-v3
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} an array of objects representing market $data
             */
            $response = Async\await($this->publicGetV3Symbols ($params));
            $data = $this->safe_dict($response, 'data', array());
            $contractConfig = $this->safe_dict($data, 'contractConfig', array());
            $perpetualContract = $this->safe_list($contractConfig, 'perpetualContract', array());
            // {
            //     "perpetualContract":array(
            //         {
            //             "baselinePositionValue" => "50000.0000",
            //             "crossId" => 30002,
            //             "crossSymbolId" => 10,
            //             "crossSymbolName" => "BTCUSDT",
            //             "digitMerge" => "0.1,0.2,0.4,1,2",
            //             "displayMaxLeverage" => "100",
            //             "displayMinLeverage" => "1",
            //             "enableDisplay" => true,
            //             "enableOpenPosition" => true,
            //             "enableTrade" => true,
            //             "fundingImpactMarginNotional" => "6",
            //             "fundingInterestRate" => "0.0003",
            //             "incrementalInitialMarginRate" => "0.00250",
            //             "incrementalMaintenanceMarginRate" => "0.00100",
            //             "incrementalPositionValue" => "50000.0000",
            //             "initialMarginRate" => "0.01",
            //             "maintenanceMarginRate" => "0.005",
            //             "maxOrderSize" => "50",
            //             "maxPositionSize" => "100",
            //             "minOrderSize" => "0.0010",
            //             "maxMarketPriceRange" => "0.025",
            //             "settleAssetId" => "USDT",
            //             "baseTokenId" => "BTC",
            //             "stepSize" => "0.001",
            //             "symbol" => "BTC-USDT",
            //             "symbolDisplayName" => "BTCUSDT",
            //             "tickSize" => "0.1",
            //             "maxMaintenanceMarginRate" => "0.5000",
            //             "maxPositionValue" => "5000000.0000",
            //             "tagIconUrl" => "https://static-pro.apex.exchange/icon/LABLE_HOT.svg",
            //             "tag" => "HOT",
            //             "riskTip" => false,
            //             "defaultInitialMarginRate" => "0.05",
            //             "klineStartTime" => 0,
            //             "maxMarketSizeBuffer" => "0.98",
            //             "enableFundingSettlement" => true,
            //             "indexPriceDecimals" => 2,
            //             "indexPriceVarRate" => "0.001",
            //             "openPositionOiLimitRate" => "0.05",
            //             "fundingMaxRate" => "0.000234",
            //             "fundingMinRate" => "-0.000234",
            //             "fundingMaxValue" => "",
            //             "enableFundingMxValue" => true,
            //             "l2PairId" => "50001",
            //             "settleTimeStamp" => 0,
            //             "isPrelaunch" => false,
            //             "riskLimitConfig" => array(),
            //             "category" => "L1"
            //         }
            //     )
            // }
            return $this->parse_markets($perpetualContract);
        }) ();
    }

    public function parse_market(array $market): array {
        $id = $this->safe_string($market, 'symbol');
        $id2 = $this->safe_string($market, 'crossSymbolName');
        $quoteId = $this->safe_string($market, 'l2PairId');
        $baseId = $this->safe_string($market, 'baseTokenId');
        $quote = $this->safe_string($market, 'settleAssetId');
        $base = $this->safe_currency_code($baseId);
        $settleId = $this->safe_string($market, 'settleAssetId');
        $settle = $this->safe_currency_code($settleId);
        $symbol = $baseId . '/' . $quote . ':' . $settle;
        $expiry = 0;
        $takerFee = $this->parse_number('0.0002');
        $makerFee = $this->parse_number('0.0005');
        return $this->safe_market_structure(array(
            'id' => $id,
            'id2' => $id2,
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'settle' => $settle,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'settleId' => $settleId,
            'type' => 'swap',
            'spot' => false,
            'margin' => null,
            'swap' => true,
            'future' => false,
            'option' => false,
            'active' => $this->safe_bool($market, 'enableTrade'),
            'contract' => true,
            'linear' => true,
            'inverse' => false,
            'taker' => $takerFee,
            'maker' => $makerFee,
            'contractSize' => $this->safe_number($market, 'minOrderSize'),
            'expiry' => ($expiry === 0) ? null : $expiry,
            'expiryDatetime' => ($expiry === 0) ? null : $this->iso8601($expiry),
            'strike' => null,
            'optionType' => null,
            'precision' => array(
                'amount' => $this->safe_number($market, 'stepSize'),
                'price' => $this->safe_number($market, 'tickSize'),
            ),
            'limits' => array(
                'leverage' => array(
                    'min' => $this->safe_number($market, 'displayMinLeverage'),
                    'max' => $this->safe_number($market, 'displayMaxLeverage'),
                ),
                'amount' => array(
                    'min' => $this->safe_number($market, 'minOrderSize'),
                    'max' => $this->safe_number($market, 'maxOrderSize'),
                ),
                'price' => array(
                    'min' => null,
                    'max' => null,
                ),
                'cost' => array(
                    'min' => null,
                    'max' => null,
                ),
            ),
            'created' => null,
            'info' => $market,
        ));
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        //
        // {
        //     "symbol" => "BTCUSDT",
        //     "price24hPcnt" => "0.450141",
        //     "lastPrice" => "43511.50",
        //     "highPrice24h" => "43513.50",
        //     "lowPrice24h" => "29996.00",
        //     "markPrice" => "43513.50",
        //     "indexPrice" => "40828.94",
        //     "openInterest" => "2036854775808",
        //     "turnover24h" => "5626085.23749999",
        //     "volume24h" => "169.317",
        //     "fundingRate" => "0",
        //     "predictedFundingRate" => "0",
        //     "nextFundingTime" => "10:00:00",
        //     "tradeCount" => 100
        // }
        //
        $timestamp = $this->milliseconds();
        $marketId = $this->safe_string($ticker, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $this->safe_symbol($marketId, $market);
        $last = $this->safe_string($ticker, 'lastPrice');
        $percentage = $this->safe_string($ticker, 'price24hPcnt');
        $quoteVolume = $this->safe_string($ticker, 'turnover24h');
        $baseVolume = $this->safe_string($ticker, 'volume24h');
        $high = $this->safe_string($ticker, 'highPrice24h');
        $low = $this->safe_string($ticker, 'lowPrice24h');
        return $this->safe_ticker(array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $high,
            'low' => $low,
            'bid' => null,
            'bidVolume' => null,
            'ask' => null,
            'askVolume' => null,
            'vwap' => null,
            'open' => null,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => null,
            'percentage' => $percentage,
            'average' => null,
            'baseVolume' => $baseVolume,
            'quoteVolume' => $quoteVolume,
            'markPrice' => $this->safe_string($ticker, 'markPrice'),
            'indexPrice' => $this->safe_string($ticker, 'indexPrice'),
            '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://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-ticker-data-v3
             *
             * @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(
                'symbol' => $market['id2'],
            );
            $response = Async\await($this->publicGetV3Ticker ($this->extend($request, $params)));
            $tickers = $this->safe_list($response, 'data', array());
            $rawTicker = $this->safe_dict($tickers, 0, array());
            return $this->parse_ticker($rawTicker, $market);
        }) ();
    }

    public function fetch_tickers(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
             *
             * @see https://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-ticker-data-v3
             *
             * @param {string} $symbols 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());
            $response = Async\await($this->publicGetV3DataAllTickerInfo ($params));
            $tickers = $this->safe_list($response, 'data', array());
            return $this->parse_tickers($tickers, $symbols);
        }) ();
    }

    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://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-candlestick-chart-$data-v3
             *
             * @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 {int} [$params->until] timestamp in ms of the latest candle to fetch
             * @return {int[][]} A list of candles ordered, open, high, low, close, volume
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'interval' => $this->safe_string($this->timeframes, $timeframe, $timeframe),
                'symbol' => $market['id2'],
            );
            if ($limit === null) {
                $limit = 200; // default is 200 when requested with `$since`
            }
            $request['limit'] = $limit; // max 200, default 200
            list($request, $params) = $this->handle_until_option('end', $request, $params, 0.001);
            if ($since !== null) {
                $request['start'] = (int) floor($since / 1000);
            }
            $response = Async\await($this->publicGetV3Klines ($this->extend($request, $params)));
            $data = $this->safe_dict($response, 'data', array());
            $OHLCVs = $this->safe_list($data, $market['id2'], array());
            return $this->parse_ohlcvs($OHLCVs, $market, $timeframe, $since, $limit);
        }) ();
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        //
        //  {
        //     "start" => 1647511440000,
        //     "symbol" => "BTC-USD",
        //     "interval" => "1",
        //     "low" => "40000",
        //     "high" => "45000",
        //     "open" => "45000",
        //     "close" => "40000",
        //     "volume" => "1.002",
        //     "turnover" => "3"
        //  } array("s":"BTCUSDT","i":"1","t":1741265880000,"c":"90235","h":"90235","l":"90156","o":"90156","v":"0.052","tr":"4690.4466")
        //
        return array(
            $this->safe_integer_n($ohlcv, array( 'start', 't' )),
            $this->safe_number_n($ohlcv, array( 'open', 'o' )),
            $this->safe_number_n($ohlcv, array( 'high', 'h' )),
            $this->safe_number_n($ohlcv, array( 'low', 'l' )),
            $this->safe_number_n($ohlcv, array( 'close', 'c' )),
            $this->safe_number_n($ohlcv, array( 'volume', 'v' )),
        );
    }

    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://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-$market-depth-v3
             *
             * @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
             * @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(
                'symbol' => $market['id2'],
            );
            if ($limit === null) {
                $limit = 100; // default is 200 when requested with `since`
            }
            $request['limit'] = $limit; // max 100, default 100
            $response = Async\await($this->publicGetV3Depth ($this->extend($request, $params)));
            //
            // {
            //     "a" => array(
            //     array(
            //         "96576.3",
            //         "0.399"
            //     ),
            //     array(
            //         "96577.6",
            //         "0.106"
            //     )
            // ),
            //     "b" => array(
            //     array(
            //         "96565.2",
            //         "0.131"
            //     ),
            //     array(
            //         "96565.1",
            //         "0.038"
            //     )
            // ),
            //     "s" => "BTCUSDT",
            //     "u" => 18665465
            // }
            //
            $data = $this->safe_dict($response, 'data', array());
            $timestamp = $this->milliseconds();
            $orderbook = $this->parse_order_book($data, $market['symbol'], $timestamp, 'b', 'a');
            $orderbook['nonce'] = $this->safe_integer($data, 'u');
            return $orderbook;
        }) ();
    }

    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://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-newest-trading-data-v3
             *
             * @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 {int} [$params->until] the latest time in ms to fetch $trades for
             * @param {boolean} [$params->paginate] 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());
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id2'],
            );
            if ($limit === null) {
                $limit = 500; // default is 50
            }
            $request['limit'] = $limit;
            $response = Async\await($this->publicGetV3Trades ($this->extend($request, $params)));
            //
            // array(
            //  array(
            //      "i" => "993f7f85-9215-5723-9078-2186ae140847",
            //      "p" => "96534.3",
            //      "S" => "Sell",
            //      "v" => "0.261",
            //      "s" => "BTCUSDT",
            //      "T" => 1739118072710
            //  ),
            //  {
            //      "i" => "c947c9cf-8c18-5784-89c3-91bdf86ddde8",
            //      "p" => "96513.5",
            //      "S" => "Sell",
            //      "v" => "0.042",
            //      "s" => "BTCUSDT",
            //      "T" => 1739118075944
            //  }
            //  )
            //
            $trades = $this->safe_list($response, 'data', array());
            return $this->parse_trades($trades, $market, $since, $limit);
        }) ();
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // array(
        //  {
        //      "i" => "993f7f85-9215-5723-9078-2186ae140847",
        //      "p" => "96534.3",
        //      "S" => "Sell",
        //      "v" => "0.261",
        //      "s" => "BTCUSDT",
        //      "T" => 1739118072710
        //  }
        //  )
        //
        $marketId = $this->safe_string_n($trade, array( 's', 'symbol' ));
        $market = $this->safe_market($marketId, $market);
        $id = $this->safe_string_n($trade, array( 'i', 'id' ));
        $timestamp = $this->safe_integer_n($trade, array( 't', 'T', 'createdAt' ));
        $priceString = $this->safe_string_n($trade, array( 'p', 'price' ));
        $amountString = $this->safe_string_n($trade, array( 'v', 'size' ));
        $side = $this->safe_string_lower_n($trade, array( 'S', 'side' ));
        $type = $this->safe_string_n($trade, array( 'type' ));
        $fee = $this->safe_string_n($trade, array( 'fee' ));
        return $this->safe_trade(array(
            'info' => $trade,
            'id' => $id,
            'order' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $market['symbol'],
            'type' => $type,
            'takerOrMaker' => null,
            'side' => $side,
            'price' => $priceString,
            'amount' => $amountString,
            'cost' => null,
            'fee' => $fee,
        ), $market);
    }

    public function fetch_open_interest(string $symbol, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             * retrieves the open interest of a contract trading pair
             *
             * @see https://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-ticker-data-v3
             *
             * @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);
            $request = array(
                'symbol' => $market['id2'],
            );
            $response = Async\await($this->publicGetV3Ticker ($this->extend($request, $params)));
            $tickers = $this->safe_list($response, 'data', array());
            $rawTicker = $this->safe_dict($tickers, 0, array());
            return $this->parse_open_interest($rawTicker, $market);
        }) ();
    }

    public function parse_open_interest($interest, ?array $market = null) {
        //
        // {
        //     "symbol" => "BTCUSDT",
        //     "price24hPcnt" => "0.450141",
        //     "lastPrice" => "43511.50",
        //     "highPrice24h" => "43513.50",
        //     "lowPrice24h" => "29996.00",
        //     "markPrice" => "43513.50",
        //     "indexPrice" => "40828.94",
        //     "openInterest" => "2036854775808",
        //     "turnover24h" => "5626085.23749999",
        //     "volume24h" => "169.317",
        //     "fundingRate" => "0",
        //     "predictedFundingRate" => "0",
        //     "nextFundingTime" => "10:00:00",
        //     "tradeCount" => 100
        // }
        //
        $timestamp = $this->milliseconds();
        $marketId = $this->safe_string($interest, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $this->safe_symbol($marketId, $market);
        return $this->safe_open_interest(array(
            'symbol' => $symbol,
            'openInterestAmount' => $this->safe_string($interest, 'openInterest'),
            'openInterestValue' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'info' => $interest,
        ), $market);
    }

    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://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-funding-rate-history-v3
             *
             * @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 {int} [$params->until] $timestamp in ms of the latest funding rate
             * @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());
            $request = array();
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
            if ($since !== null) {
                $request['beginTimeInclusive'] = $since;
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $page = $this->safe_integer($params, 'page');
            if ($page !== null) {
                $request['page'] = $page;
            }
            $endTimeExclusive = $this->safe_integer_n($params, array( 'endTime', 'endTimeExclusive', 'until' ));
            if ($endTimeExclusive !== null) {
                $request['endTimeExclusive'] = $endTimeExclusive;
            }
            $response = Async\await($this->publicGetV3HistoryFunding ($this->extend($request, $params)));
            //
            // {
            //     "historyFunds" => array(
            //     {
            //         "symbol" => "BTC-USD",
            //         "rate" => "0.0000125000",
            //         "price" => "31297.5000008009374142",
            //         "fundingTime" => 12315555,
            //         "fundingTimestamp" => 12315555
            //     }
            // ),
            //     "totalSize" => 11
            // }
            //
            $rates = array();
            $data = $this->safe_dict($response, 'data', array());
            $resultList = $this->safe_list($data, 'historyFunds', array());
            for ($i = 0; $i < count($resultList); $i++) {
                $entry = $resultList[$i];
                $timestamp = $this->safe_integer($entry, 'fundingTimestamp');
                $marketId = $this->safe_string($entry, 'symbol');
                $rates[] = array(
                    'info' => $entry,
                    'symbol' => $this->safe_symbol($marketId, $market),
                    'fundingRate' => $this->safe_number($entry, 'rate'),
                    'timestamp' => $timestamp,
                    'datetime' => $this->iso8601($timestamp),
                );
            }
            $sorted = $this->sort_by($rates, 'timestamp');
            return $this->filter_by_symbol_since_limit($sorted, $symbol, $since, $limit);
        }) ();
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        // {
        //     "id" => "1234",
        //     "clientId" => "1234",
        //     "accountId" => "12345",
        //     "symbol" => "BTC-USD",
        //     "side" => "SELL",
        //     "price" => "18000",
        //     "limitFee" => "100",
        //     "fee" => "100",
        //     "triggerPrice" => "1.2",
        //     "trailingPercent" => "0.12",
        //     "size" => "100",
        //     "remainingSize" => "100",
        //     "type" => "LIMIT",
        //     "createdAt" => 1647502440973,
        //     "updatedTime" => 1647502440973,
        //     "expiresAt" => 1647502440973,
        //     "status" => "PENDING",
        //     "timeInForce" => "GOOD_TIL_CANCEL",
        //     "postOnly" => false,
        //     "reduceOnly" => false,
        //     "stopPnl" => false,
        //     "latestMatchFillPrice" => "reason",
        //     "cumMatchFillSize" => "0.1",
        //     "cumMatchFillValue" => "1000",
        //     "cumMatchFillFee" => "1",
        //     "cumSuccessFillSize" => "0.1",
        //     "cumSuccessFillValue" => "1000",
        //     "cumSuccessFillFee" => "1",
        //     "triggerPriceType" => "INDEX",
        //     "isOpenTpslOrder" => true,
        //     "isSetOpenTp" => true,
        //     "isSetOpenSl" => false,
        //     "openTpParam" => array(
        //     "side" => "SELL",
        //         "price" => "18000",
        //         "limitFee" => "100",
        //         "clientOrderId" => "111100",
        //         "triggerPrice" => "1.2",
        //         "trailingPercent" => "0.12",
        //         "size" => "100"
        // ),
        //     "openSlParam" => {
        //     "side" => "SELL",
        //         "price" => "18000",
        //         "limitFee" => "100",
        //         "clientOrderId" => "111100",
        //         "triggerPrice" => "1.2",
        //         "trailingPercent" => "0.12",
        //         "size" => "100"
        // }
        // }
        //
        $timestamp = $this->safe_integer($order, 'createdAt');
        $orderId = $this->safe_string($order, 'id');
        $clientOrderId = $this->safe_string($order, 'clientId');
        $marketId = $this->safe_string($order, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $price = $this->safe_string($order, 'price');
        $amount = $this->safe_string($order, 'size');
        $orderType = $this->safe_string($order, 'type');
        $status = $this->safe_string($order, 'status');
        $side = $this->safe_string_lower($order, 'side');
        // $average = $this->omit_zero($this->safe_string($order, 'avg_fill_price'));
        $remaining = $this->omit_zero($this->safe_string($order, 'remainingSize'));
        $lastUpdateTimestamp = $this->safe_integer($order, 'updatedTime');
        return $this->safe_order(array(
            'id' => $orderId,
            'clientOrderId' => $clientOrderId,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'lastUpdateTimestamp' => $lastUpdateTimestamp,
            'status' => $this->parse_order_status($status),
            'symbol' => $symbol,
            'type' => $this->parse_order_type($orderType),
            'timeInForce' => $this->parse_time_in_force($this->safe_string($order, 'timeInForce')),
            'postOnly' => $this->safe_bool($order, 'postOnly'),
            'reduceOnly' => $this->safe_bool($order, 'reduceOnly'),
            'side' => $side,
            'price' => $price,
            'triggerPrice' => $this->safe_string($order, 'triggerPrice'),
            'takeProfitPrice' => null,
            'stopLossPrice' => null,
            'average' => null,
            'amount' => $amount,
            'filled' => null,
            'remaining' => $remaining,
            'cost' => null,
            'trades' => null,
            'fee' => array(
                'cost' => $this->safe_string($order, 'fee'),
                'currency' => $market['settleId'],
            ),
            'info' => $order,
        ), $market);
    }

    public function parse_time_in_force(?string $timeInForce) {
        $timeInForces = array(
            'GOOD_TIL_CANCEL' => 'GOOD_TIL_CANCEL',
            'FILL_OR_KILL' => 'FILL_OR_KILL',
            'IMMEDIATE_OR_CANCEL' => 'IMMEDIATE_OR_CANCEL',
            'POST_ONLY' => 'POST_ONLY',
        );
        return $this->safe_string($timeInForces, $timeInForce, null);
    }

    public function parse_order_status(?string $status) {
        if ($status !== null) {
            $statuses = array(
                'PENDING' => 'open',
                'OPEN' => 'open',
                'FILLED' => 'filled',
                'CANCELING' => 'canceled',
                'CANCELED' => 'canceled',
                'UNTRIGGERED' => 'open',
            );
            return $this->safe_string($statuses, $status, $status);
        }
        return $status;
    }

    public function parse_order_type(?string $type) {
        $types = array(
            'LIMIT' => 'LIMIT',
            'MARKET' => 'MARKET',
            'STOP_LIMIT' => 'STOP_LIMIT',
            'STOP_MARKET' => 'STOP_MARKET',
            'TAKE_PROFIT_LIMIT' => 'TAKE_PROFIT_LIMIT',
            'TAKE_PROFIT_MARKET' => 'TAKE_PROFIT_MARKET',
        );
        return $this->safe_string_upper($types, $type, $type);
    }

    public function safe_market(?string $marketId = null, ?array $market = null, ?string $delimiter = null, ?string $marketType = null): array {
        if ($market === null && $marketId !== null) {
            if (is_array($this->markets) && array_key_exists($marketId, $this->markets)) {
                $market = $this->markets[$marketId];
            } elseif (is_array($this->markets_by_id) && array_key_exists($marketId, $this->markets_by_id)) {
                $market = $this->markets_by_id[$marketId];
            } else {
                $newMarketId = $this->add_hyphen_before_usdt($marketId);
                if (is_array($this->markets_by_id) && array_key_exists($newMarketId, $this->markets_by_id)) {
                    $markets = $this->markets_by_id[$newMarketId];
                    $numMarkets = count($markets);
                    if ($numMarkets > 0) {
                        if ($this->markets_by_id[$newMarketId][0]['id2'] === $marketId) {
                            $market = $this->markets_by_id[$newMarketId][0];
                        }
                    }
                }
            }
        }
        return parent::safe_market($marketId, $market, $delimiter, $marketType);
    }

    public function generate_random_client_id_omni(string $_accountId) {
        $accountId = $_accountId || (string) $this->rand_number(12);
        return 'apexomni-' . $accountId . '-' . (string) $this->milliseconds() . '-' . (string) $this->rand_number(6);
    }

    public function add_hyphen_before_usdt(string $symbol) {
        $uppercaseSymbol = strtoupper($symbol);
        $index = mb_strpos($uppercaseSymbol, 'USDT');
        $symbolChar = $this->safe_string($symbol, $index - 1);
        if ($index > 0 && $symbolChar !== '-') {
            return mb_substr($symbol, 0, $index - 0) . '-' . mb_substr($symbol, $index);
        }
        return $symbol;
    }

    public function get_seeds() {
        $seeds = $this->safe_string($this->options, 'seeds');
        if ($seeds === null) {
            throw new ArgumentsRequired($this->id . ' the "seeds" key is required in the options to access private endpoints. You can find it in API Management > Omni Key, and then set it.options["seeds"] = XXXX');
        }
        return $seeds;
    }

    public function get_account_id() {
        return Async\async(function ()  {
            $accountId = $this->safe_string($this->options, 'accountId', '0');
            if ($accountId === '0') {
                $accountData = Async\await($this->fetch_account());
                $this->options['accountId'] = $this->safe_string($accountData, 'id', '0');
            }
            return $this->options['accountId'];
        }) ();
    }

    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://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-post-creating-orders
             *
             * @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 fullfilled, in units of the quote currency, ignored in $market orders
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {float} [$params->triggerPrice] The $price a trigger order is triggered at
             * @param {string} [$params->timeInForce] "GTC", "IOC", or "POST_ONLY"
             * @param {bool} [$params->postOnly] true or false
             * @param {bool} [$params->reduceOnly] Ensures that the executed order does not flip the opened position.
             * @param {string} [$params->clientOrderId] a unique id for the order
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $orderType = strtoupper($type);
            $orderSide = strtoupper($side);
            $orderSize = $this->amount_to_precision($symbol, $amount);
            $orderPrice = '0';
            if ($price !== null) {
                $orderPrice = $this->price_to_precision($symbol, $price);
            }
            $fees = $this->safe_dict($this->fees, 'swap', array());
            $taker = $this->safe_number($fees, 'taker', 0.0005);
            $maker = $this->safe_number($fees, 'maker', 0.0002);
            $limitFee = $this->decimal_to_precision(Precise::string_add(Precise::string_mul(Precise::string_mul($orderPrice, $orderSize), (string) $taker), (string) $market['precision']['price']), TRUNCATE, $market['precision']['price'], $this->precisionMode, $this->paddingMode);
            $timeNow = $this->milliseconds();
            // $triggerPrice = $this->safe_string_2($params, 'triggerPrice', 'stopPrice');
            $isMarket = $orderType === 'MARKET';
            if ($isMarket && ($price === null)) {
                throw new ArgumentsRequired($this->id . ' createOrder() requires a $price argument for $market orders');
            }
            $timeInForce = $this->safe_string_upper($params, 'timeInForce');
            $postOnly = $this->is_post_only($isMarket, null, $params);
            if ($timeInForce === null) {
                $timeInForce = 'GOOD_TIL_CANCEL';
            }
            if (!$isMarket) {
                if ($postOnly) {
                    $timeInForce = 'POST_ONLY';
                } elseif ($timeInForce === 'ioc') {
                    $timeInForce = 'IMMEDIATE_OR_CANCEL';
                }
            }
            $params = $this->omit($params, 'timeInForce');
            $params = $this->omit($params, 'postOnly');
            $clientOrderId = $this->safe_string_n($params, array( 'clientId', 'clientOrderId', 'client_order_id' ));
            $accountId = Async\await($this->get_account_id());
            if ($clientOrderId === null) {
                $clientOrderId = $this->generate_random_client_id_omni($accountId);
            }
            $params = $this->omit($params, array( 'clientId', 'clientOrderId', 'client_order_id' ));
            $orderToSign = array(
                'accountId' => $accountId,
                'slotId' => $clientOrderId,
                'nonce' => $clientOrderId,
                'pairId' => $market['quoteId'],
                'size' => $orderSize,
                'price' => $orderPrice,
                'direction' => $orderSide,
                'makerFeeRate' => (string) $maker,
                'takerFeeRate' => (string) $taker,
            );
            $signature = Async\await($this->get_zk_contract_signature_obj($this->remove0x_prefix($this->get_seeds()), $orderToSign));
            $request = array(
                'symbol' => $market['id'],
                'side' => $orderSide,
                'type' => $orderType, // LIMIT/MARKET/STOP_LIMIT/STOP_MARKET
                'size' => $orderSize,
                'price' => $orderPrice,
                'limitFee' => $limitFee,
                'expiration' => (int) floor($timeNow / 1000 + 30 * 24 * 60 * 60),
                'timeInForce' => $timeInForce,
                'clientId' => $clientOrderId,
                'brokerId' => $this->safe_string($this->options, 'brokerId', '6956'),
            );
            $request['signature'] = $signature;
            $response = Async\await($this->privatePostV3Order ($this->extend($request, $params)));
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_order($data, $market);
        }) ();
    }

    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
             * @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
             * @param {string} [$params->transferId] UUID, which is unique across the platform
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=transfer-structure transfer structure~
             */
            Async\await($this->load_markets());
            $configResponse = Async\await($this->publicGetV3Symbols ($params));
            $configData = $this->safe_dict($configResponse, 'data', array());
            $contractConfig = $this->safe_dict($configData, 'contractConfig', array());
            $contractAssets = $this->safe_list($contractConfig, 'assets', array());
            $spotConfig = $this->safe_dict($configData, 'spotConfig', array());
            $spotAssets = $this->safe_list($spotConfig, 'assets', array());
            $globalConfig = $this->safe_dict($spotConfig, 'global', array());
            $receiverAddress = $this->safe_string($globalConfig, 'contractAssetPoolEthAddress', '');
            $receiverZkAccountId = $this->safe_string($globalConfig, 'contractAssetPoolZkAccountId', '');
            $receiverSubAccountId = $this->safe_string($globalConfig, 'contractAssetPoolSubAccount', '');
            $receiverAccountId = $this->safe_string($globalConfig, 'contractAssetPoolAccountId', '');
            $accountResponse = Async\await($this->privateGetV3Account ($params));
            $accountData = $this->safe_dict($accountResponse, 'data', array());
            $spotAccount = $this->safe_dict($accountData, 'spotAccount', array());
            $zkAccountId = $this->safe_string($spotAccount, 'zkAccountId', '');
            $subAccountId = $this->safe_string($spotAccount, 'defaultSubAccountId', '0');
            $subAccounts = $this->safe_list($spotAccount, 'subAccounts', array());
            $nonce = '0';
            if (strlen($subAccounts) > 0) {
                $nonce = $this->safe_string($subAccounts[0], 'nonce', '0');
            }
            $ethAddress = $this->safe_string($accountData, 'ethereumAddress', '');
            $accountId = $this->safe_string($accountData, 'id', '');
            $currency = array();
            $assets = array();
            if ($fromAccount !== null && strtolower($fromAccount) === 'contract') {
                $assets = $contractAssets;
            } else {
                $assets = $spotAssets;
            }
            for ($i = 0; $i < count($assets); $i++) {
                if ($this->safe_string($assets[$i], 'token', '') === $code) {
                    $currency = $assets[$i];
                }
            }
            $tokenId = $this->safe_string($currency, 'tokenId', '');
            $amountNumber = $this->parse_to_int($amount * (pow(10, $this->safe_number($currency, 'decimals', 0))));
            $timestampSeconds = $this->parse_to_int($this->milliseconds() / 1000);
            $clientOrderId = $this->safe_string_n($params, array( 'clientId', 'clientOrderId', 'client_order_id' ));
            if ($clientOrderId === null) {
                $clientOrderId = $this->generate_random_client_id_omni($this->safe_string($this->options, 'accountId'));
            }
            $params = $this->omit($params, array( 'clientId', 'clientOrderId', 'client_order_id' ));
            if ($fromAccount !== null && strtolower($fromAccount) === 'contract') {
                $formattedUint32 = '4294967295';
                $zkSignAccountId = Precise::string_mod($accountId, $formattedUint32);
                $expireTime = $timestampSeconds + 3600 * 24 * 28;
                $orderToSign = array(
                    'zkAccountId' => $zkSignAccountId,
                    'receiverAddress' => $ethAddress,
                    'subAccountId' => $subAccountId,
                    'receiverSubAccountId' => $subAccountId,
                    'tokenId' => $tokenId,
                    'amount' => (string) $amountNumber,
                    'fee' => '0',
                    'nonce' => $clientOrderId,
                    'timestampSeconds' => $expireTime,
                    'isContract' => true,
                );
                $signature = Async\await($this->get_zk_transfer_signature_obj($this->remove0x_prefix($this->get_seeds()), $orderToSign));
                $request = array(
                    'amount' => $amount,
                    'expireTime' => $expireTime,
                    'clientWithdrawId' => $clientOrderId,
                    'signature' => $signature,
                    'token' => $code,
                    'ethAddress' => $ethAddress,
                );
                $response = Async\await($this->privatePostV3ContractTransferOut ($this->extend($request, $params)));
                $data = $this->safe_dict($response, 'data', array());
                $currentTime = $this->milliseconds();
                return $this->extend($this->parse_transfer($data, $this->currency($code)), array(
                    'timestamp' => $currentTime,
                    'datetime' => $this->iso8601($currentTime),
                    'amount' => $this->parse_number($amount),
                    'fromAccount' => 'contract',
                    'toAccount' => 'spot',
                ));
            } else {
                $orderToSign = array(
                    'zkAccountId' => $zkAccountId,
                    'receiverAddress' => $receiverAddress,
                    'subAccountId' => $subAccountId,
                    'receiverSubAccountId' => $receiverSubAccountId,
                    'tokenId' => $tokenId,
                    'amount' => (string) $amountNumber,
                    'fee' => '0',
                    'nonce' => $nonce,
                    'timestampSeconds' => $timestampSeconds,
                );
                $signature = Async\await($this->get_zk_transfer_signature_obj($this->remove0x_prefix($this->get_seeds()), $orderToSign));
                $request = array(
                    'amount' => (string) $amount,
                    'timestamp' => $timestampSeconds,
                    'clientTransferId' => $clientOrderId,
                    'signature' => $signature,
                    'zkAccountId' => $zkAccountId,
                    'subAccountId' => $subAccountId,
                    'fee' => '0',
                    'token' => $code,
                    'tokenId' => $tokenId,
                    'receiverAccountId' => $receiverAccountId,
                    'receiverZkAccountId' => $receiverZkAccountId,
                    'receiverSubAccountId' => $receiverSubAccountId,
                    'receiverAddress' => $receiverAddress,
                    'nonce' => $nonce,
                );
                $response = Async\await($this->privatePostV3TransferOut ($this->extend($request, $params)));
                $data = $this->safe_dict($response, 'data', array());
                $currentTime = $this->milliseconds();
                return $this->extend($this->parse_transfer($data, $this->currency($code)), array(
                    'timestamp' => $currentTime,
                    'datetime' => $this->iso8601($currentTime),
                    'amount' => $this->parse_number($amount),
                    'fromAccount' => 'spot',
                    'toAccount' => 'contract',
                ));
            }
        }) ();
    }

    public function parse_transfer(array $transfer, ?array $currency = null): array {
        $currencyId = $this->safe_string($transfer, 'coin');
        $timestamp = $this->safe_integer($transfer, 'timestamp');
        $fromAccount = $this->safe_string($transfer, 'fromAccount');
        $toAccount = $this->safe_string($transfer, 'toAccount');
        return array(
            'info' => $transfer,
            'id' => $this->safe_string_n($transfer, array( 'transferId', 'id' )),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'currency' => $this->safe_currency_code($currencyId, $currency),
            'amount' => $this->safe_number($transfer, 'amount'),
            'fromAccount' => $fromAccount,
            'toAccount' => $toAccount,
            'status' => $this->safe_string($transfer, 'status'),
        );
    }

    public function cancel_all_orders(?string $symbol = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * cancel all open orders in a $market
             *
             * @see https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-post-cancel-all-open-orders
             *
             * @param {string} $symbol unified $market $symbol of the $market to cancel orders in
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $market = null;
            $request = array();
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['symbol'] = $market['id'];
            }
            $response = Async\await($this->privatePostV3DeleteOpenOrders ($this->extend($request, $params)));
            $data = $this->safe_dict($response, 'data', array());
            return array( $this->parse_order($data, $market) );
        }) ();
    }

    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://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-post-cancel-order
             *
             * @param {string} $id order $id
             * @param $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
             */
            $request = array();
            $clientOrderId = $this->safe_string_n($params, array( 'clientId', 'clientOrderId', 'client_order_id' ));
            $response = null;
            if ($clientOrderId !== null) {
                $request['id'] = $clientOrderId;
                $params = $this->omit($params, array( 'clientId', 'clientOrderId', 'client_order_id' ));
                $response = Async\await($this->privatePostV3DeleteClientOrderId ($this->extend($request, $params)));
            } else {
                $request['id'] = $id;
                $response = Async\await($this->privatePostV3DeleteOrder ($this->extend($request, $params)));
            }
            $data = $this->safe_dict($response, 'data', array());
            return $this->safe_order($data);
        }) ();
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $params) {
            /**
             * fetches information on an order made by the user
             *
             * @see https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-order-$id
             * @see https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-order-by-clientorderid
             *
             * @param {string} $id the 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 {string} [$params->clientOrderId] a unique $id for the order
             * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $request = array();
            $clientOrderId = $this->safe_string_n($params, array( 'clientId', 'clientOrderId', 'client_order_id' ));
            $response = null;
            if ($clientOrderId !== null) {
                $request['id'] = $clientOrderId;
                $params = $this->omit($params, array( 'clientId', 'clientOrderId', 'client_order_id' ));
                $response = Async\await($this->privateGetV3OrderByClientOrderId ($this->extend($request, $params)));
            } else {
                $request['id'] = $id;
                $response = Async\await($this->privateGetV3Order ($this->extend($request, $params)));
            }
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_order($data);
        }) ();
    }

    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) {
            /**
             * fetches information on multiple $orders made by the user
             *
             * @see https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-open-$orders
             *
             * @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
             * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $response = Async\await($this->privateGetV3OpenOrders ($params));
            $orders = $this->safe_list($response, 'data', array());
            return $this->parse_orders($orders, null, $since, $limit);
        }) ();
    }

    public function fetch_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 $orders made by the user *classic accounts only*
             *
             * @see https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-all-order-history
             *
             * @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, default 100
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {array} [$params->until] end time, ms
             * @param {boolean} [$params->status] "PENDING", "OPEN", "FILLED", "CANCELED", "EXPIRED", "UNTRIGGERED"
             * @param {boolean} [$params->side] BUY or SELL
             * @param {string} [$params->type] "LIMIT", "MARKET","STOP_LIMIT", "STOP_MARKET", "TAKE_PROFIT_LIMIT","TAKE_PROFIT_MARKET"
             * @param {string} [$params->orderType] "ACTIVE","CONDITION","HISTORY"
             * @param {boolean} [$params->page] Page numbers start from 0
             * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $request = array();
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['symbol'] = $market['id'];
            }
            if ($since !== null) {
                $request['beginTimeInclusive'] = $since;
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $endTimeExclusive = $this->safe_integer_n($params, array( 'endTime', 'endTimeExclusive', 'until' ));
            if ($endTimeExclusive !== null) {
                $request['endTimeExclusive'] = $endTimeExclusive;
                $params = $this->omit($params, array( 'endTime', 'endTimeExclusive', 'until' ));
            }
            $response = Async\await($this->privateGetV3HistoryOrders ($this->extend($request, $params)));
            $data = $this->safe_dict($response, 'data', array());
            $orders = $this->safe_list($data, 'orders', array());
            return $this->parse_orders($orders, $market, $since, $limit);
        }) ();
    }

    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://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-trade-history
             *
             * @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~
             */
            Async\await($this->load_markets());
            $request = array();
            $clientOrderId = $this->safe_string_2($params, 'clientOrderId', 'clientId');
            if ($clientOrderId !== null) {
                $request['clientOrderId'] = $clientOrderId;
            } else {
                $request['orderId'] = $id;
            }
            $params = $this->omit($params, array( 'clientOrderId', 'clientId' ));
            $response = Async\await($this->privateGetV3OrderFills ($this->extend($request, $params)));
            $data = $this->safe_dict($response, 'data', array());
            $orders = $this->safe_list($data, 'orders', array());
            return $this->parse_trades($orders, null, $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) {
            /**
             * fetches information on multiple $orders made by the user *classic accounts only*
             *
             * @see https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-trade-history
             *
             * @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, default 100
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {array} [$params->until] end time
             * @param {boolean} [$params->side] BUY or SELL
             * @param {string} [$params->orderType] "LIMIT", "MARKET","STOP_LIMIT", "STOP_MARKET", "TAKE_PROFIT_LIMIT","TAKE_PROFIT_MARKET"
             * @param {boolean} [$params->page] Page numbers start from 0
             * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
             */
            Async\await($this->load_markets());
            $request = array();
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['symbol'] = $market['id'];
            }
            if ($since !== null) {
                $request['beginTimeInclusive'] = $since;
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $endTimeExclusive = $this->safe_integer_n($params, array( 'endTime', 'endTimeExclusive', 'until' ));
            if ($endTimeExclusive !== null) {
                $request['endTimeExclusive'] = $endTimeExclusive;
                $params = $this->omit($params, array( 'endTime', 'endTimeExclusive', 'until' ));
            }
            $response = Async\await($this->privateGetV3Fills ($this->extend($request, $params)));
            $data = $this->safe_dict($response, 'data', array());
            $orders = $this->safe_list($data, 'orders', array());
            return $this->parse_trades($orders, $market, $since, $limit);
        }) ();
    }

    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) {
            /**
             * fetches information on multiple orders made by the user *classic accounts only*
             *
             * @see https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-funding-rate
             *
             * @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, default 100
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {array} [$params->until] end time, ms
             * @param {boolean} [$params->side] BUY or SELL
             * @param {boolean} [$params->page] Page numbers start from 0
             * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=funding-history-structure trade structures~
             */
            Async\await($this->load_markets());
            $request = array();
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['symbol'] = $market['id'];
            }
            if ($since !== null) {
                $request['beginTimeInclusive'] = $since;
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $endTimeExclusive = $this->safe_integer_n($params, array( 'endTime', 'endTimeExclusive', 'until' ));
            if ($endTimeExclusive !== null) {
                $params = $this->omit($params, array( 'endTime', 'endTimeExclusive', 'until' ));
                $request['endTimeExclusive'] = $endTimeExclusive;
            }
            $response = Async\await($this->privateGetV3Funding ($this->extend($request, $params)));
            $data = $this->safe_dict($response, 'data', array());
            $fundingValues = $this->safe_list($data, 'fundingValues', array());
            return $this->parse_incomes($fundingValues, $market, $since, $limit);
        }) ();
    }

    public function parse_income($income, ?array $market = null) {
        //
        // {
        //     "id" => "1234",
        //     "symbol" => "BTC-USDT",
        //     "fundingValue" => "10000",
        //     "rate" => "0.0000125000",
        //     "positionSize" => "500",
        //     "price" => "90",
        //     "side" => "LONG",
        //     "status" => "SUCCESS",
        //     "fundingTime" => 1647502440973,
        //     "transactionId" => "1234556"
        // }
        //
        $marketId = $this->safe_string($income, 'symbol');
        $market = $this->safe_market($marketId, $market, null, 'contract');
        $code = 'USDT';
        $timestamp = $this->safe_integer($income, 'fundingTime');
        return array(
            'info' => $income,
            'symbol' => $this->safe_symbol($marketId, $market),
            'code' => $code,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'id' => $this->safe_string($income, 'id'),
            'amount' => $this->safe_number($income, 'fundingValue'),
            'rate' => $this->safe_number($income, 'rate'),
        );
    }

    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://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-post-sets-the-initial-margin-rate-of-a-contract
             *
             * @param {float} $leverage the rate of $leverage
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} $response from the exchange
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' setLeverage() requires a $symbol argument');
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $leverageString = $this->number_to_string($leverage);
            $initialMarginRate = Precise::string_div('1', $leverageString, 4);
            $request = array(
                'symbol' => $market['id'],
                'initialMarginRate' => $initialMarginRate,
            );
            $response = Async\await($this->privatePostV3SetInitialMarginRate ($this->extend($request, $params)));
            $data = $this->safe_dict($response, 'data', array());
            return $data;
        }) ();
    }

    public function fetch_positions(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetch all open $positions
             *
             * @see https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-retrieve-user-account-$data
             *
             * @param {string[]} [$symbols] list of unified market $symbols
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=position-structure position structure~
             */
            Async\await($this->load_markets());
            $response = Async\await($this->privateGetV3Account ($params));
            $data = $this->safe_dict($response, 'data', array());
            $positions = $this->safe_list($data, 'positions', array());
            return $this->parse_positions($positions, $symbols);
        }) ();
    }

    public function parse_position(array $position, ?array $market = null) {
        //
        // {
        //     "symbol" => "BTC-USDT",
        //     "status" => "",
        //     "side" => "LONG",
        //     "size" => "0.000",
        //     "entryPrice" => "0.00",
        //     "exitPrice" => "",
        //     "createdAt" => 1690366452416,
        //     "updatedTime" => 1690366452416,
        //     "fee" => "0.000000",
        //     "fundingFee" => "0.000000",
        //     "lightNumbers" => "",
        //     "customInitialMarginRate" => "0"
        // }
        $marketId = $this->safe_string($position, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $side = $this->safe_string_lower($position, 'side');
        $quantity = $this->safe_string($position, 'size');
        $timestamp = $this->safe_integer($position, 'updatedTime');
        $leverage = 20;
        $customInitialMarginRate = $this->safe_string_n($position, array( 'customInitialMarginRate', 'customImr' ), '0');
        if ($this->precision_from_string($customInitialMarginRate) !== 0) {
            $leverage = $this->parse_to_int(Precise::string_div('1', $customInitialMarginRate, 4));
        }
        return $this->safe_position(array(
            'info' => $position,
            'id' => $this->safe_string($position, 'id'),
            'symbol' => $symbol,
            'entryPrice' => $this->safe_string($position, 'entryPrice'),
            'markPrice' => null,
            'notional' => null,
            'collateral' => null,
            'unrealizedPnl' => null,
            'side' => $side,
            'contracts' => $this->parse_number($quantity),
            'contractSize' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'hedged' => null,
            'maintenanceMargin' => null,
            'maintenanceMarginPercentage' => null,
            'initialMargin' => null,
            'initialMarginPercentage' => null,
            'leverage' => $leverage,
            'liquidationPrice' => null,
            'marginRatio' => null,
            'marginMode' => null,
            'percentage' => null,
        ));
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $url = $this->implode_hostname($this->urls['api'][$api]) . '/' . $path;
        $headers = array(
            'User-Agent' => 'apex-CCXT',
            'Accept' => 'application/json',
            'Content-Type' => 'application/x-www-form-urlencoded',
        );
        $signPath = '/api/' . $path;
        $signBody = $body;
        if (strtoupper($method) !== 'POST') {
            if ($params) {
                $signPath .= '?' . $this->rawencode($params);
                $url .= '?' . $this->rawencode($params);
            }
        } else {
            $sortedQuery = $this->keysort($params);
            $signBody = $this->rawencode($sortedQuery);
        }
        if ($api === 'private') {
            $this->check_required_credentials();
            $timestamp = (string) $this->milliseconds();
            $messageString = $timestamp . strtoupper($method) . $signPath;
            if ($signBody !== null) {
                $messageString = $messageString . $signBody;
            }
            $signature = $this->hmac($this->encode($messageString), $this->encode(base64_encode($this->secret)), 'sha256', 'base64');
            $headers['APEX-SIGNATURE'] = $signature;
            $headers['APEX-API-KEY'] = $this->apiKey;
            $headers['APEX-TIMESTAMP'] = $timestamp;
            $headers['APEX-PASSPHRASE'] = $this->password;
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $signBody, 'headers' => $headers );
    }

    public function handle_errors(int $code, string $reason, string $url, string $method, array $headers, string $body, $response, $requestHeaders, $requestBody) {
        //
        // array("code":3,"msg":"Order price must be greater than 0. Order price is 0.","key":"ORDER_PRICE_MUST_GREETER_ZERO","detail":array("price":"0"))
        // array("code":400,"msg":"strconv.ParseInt => parsing \"dsfdfsd\" => invalid syntax","timeCost":5320995)
        //
        if ($response === null) {
            return null;
        }
        $errorCode = $this->safe_integer($response, 'code');
        if ($errorCode !== null && $errorCode !== 0) {
            $feedback = $this->id . ' ' . $body;
            $message = $this->safe_string_2($response, 'key', 'msg');
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $message, $feedback);
            $status = (string) $code;
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $status, $feedback);
            throw new ExchangeError($feedback);
        }
        return null;
    }
}
