<?php

namespace ccxt;

// 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\abstract\ascendex as Exchange;

class ascendex extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'ascendex',
            'name' => 'AscendEX',
            'countries' => array( 'SG' ), // Singapore
            // 8 requests per minute = 0.13333 per second => rateLimit = 750
            // testing 400 works
            'rateLimit' => 400,
            'certified' => false,
            'pro' => true,
            // new metainfo interface
            'has' => array(
                'CORS' => null,
                'spot' => true,
                'margin' => true,
                'swap' => true,
                'future' => false,
                'option' => false,
                'addMargin' => true,
                'cancelAllOrders' => true,
                'cancelOrder' => true,
                'createOrder' => true,
                'createOrders' => true,
                'createPostOnlyOrder' => true,
                'createReduceOnlyOrder' => true,
                'createStopLimitOrder' => true,
                'createStopMarketOrder' => true,
                'createStopOrder' => true,
                'fetchAccounts' => true,
                'fetchBalance' => true,
                'fetchClosedOrders' => true,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => true,
                'fetchDepositAddresses' => false,
                'fetchDepositAddressesByNetwork' => false,
                'fetchDeposits' => true,
                'fetchDepositsWithdrawals' => true,
                'fetchDepositWithdrawFee' => 'emulated',
                'fetchDepositWithdrawFees' => true,
                'fetchFundingHistory' => true,
                'fetchFundingRate' => 'emulated',
                'fetchFundingRateHistory' => false,
                'fetchFundingRates' => true,
                'fetchGreeks' => false,
                'fetchIndexOHLCV' => false,
                'fetchLeverage' => 'emulated',
                'fetchLeverages' => true,
                'fetchLeverageTiers' => true,
                'fetchMarginMode' => 'emulated',
                'fetchMarginModes' => true,
                'fetchMarketLeverageTiers' => 'emulated',
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => false,
                'fetchMySettlementHistory' => false,
                'fetchOHLCV' => true,
                'fetchOpenInterest' => false,
                'fetchOpenInterestHistory' => false,
                'fetchOpenOrders' => true,
                'fetchOption' => false,
                'fetchOptionChain' => false,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => false,
                'fetchPosition' => false,
                'fetchPositionMode' => false,
                'fetchPositions' => true,
                'fetchPositionsRisk' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchSettlementHistory' => false,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => false,
                'fetchTradingFees' => true,
                'fetchTransactionFee' => false,
                'fetchTransactionFees' => false,
                'fetchTransactions' => 'emulated',
                'fetchTransfer' => false,
                'fetchTransfers' => false,
                'fetchVolatilityHistory' => false,
                'fetchWithdrawal' => false,
                'fetchWithdrawals' => true,
                'reduceMargin' => true,
                'sandbox' => true,
                'setLeverage' => true,
                'setMarginMode' => true,
                'setPositionMode' => false,
                'transfer' => true,
            ),
            'timeframes' => array(
                '1m' => '1',
                '5m' => '5',
                '15m' => '15',
                '30m' => '30',
                '1h' => '60',
                '2h' => '120',
                '4h' => '240',
                '6h' => '360',
                '12h' => '720',
                '1d' => '1d',
                '1w' => '1w',
                '1M' => '1m',
            ),
            'version' => 'v2',
            'urls' => array(
                'logo' => 'https://github.com/user-attachments/assets/55bab6b9-d4ca-42a8-a0e6-fac81ae557f1',
                'api' => array(
                    'rest' => 'https://ascendex.com',
                ),
                'test' => array(
                    'rest' => 'https://api-test.ascendex-sandbox.com',
                ),
                'www' => 'https://ascendex.com',
                'doc' => array(
                    'https://ascendex.github.io/ascendex-pro-api/#ascendex-pro-api-documentation',
                ),
                'fees' => 'https://ascendex.com/en/feerate/transactionfee-traderate',
                'referral' => array(
                    'url' => 'https://ascendex.com/en-us/register?inviteCode=EL6BXBQM',
                    'discount' => 0.25,
                ),
            ),
            'api' => array(
                'v1' => array(
                    'public' => array(
                        'get' => array(
                            'assets' => 1,
                            'products' => 1,
                            'ticker' => 1,
                            'barhist/info' => 1,
                            'barhist' => 1,
                            'depth' => 1,
                            'trades' => 1,
                            'cash/assets' => 1, // not documented
                            'cash/products' => 1, // not documented
                            'margin/assets' => 1, // not documented
                            'margin/products' => 1, // not documented
                            'futures/collateral' => 1,
                            'futures/contracts' => 1,
                            'futures/ref-px' => 1,
                            'futures/market-data' => 1,
                            'futures/funding-rates' => 1,
                            'risk-limit-info' => 1,
                            'exchange-info' => 1,
                        ),
                    ),
                    'private' => array(
                        'get' => array(
                            'info' => 1,
                            'wallet/transactions' => 1,
                            'wallet/deposit/address' => 1,
                            'data/balance/snapshot' => 1,
                            'data/balance/history' => 1,
                        ),
                        'accountCategory' => array(
                            'get' => array(
                                'balance' => 1,
                                'order/open' => 1,
                                'order/status' => 1,
                                'order/hist/current' => 1,
                                'risk' => 1,
                            ),
                            'post' => array(
                                'order' => 1,
                                'order/batch' => 1,
                            ),
                            'delete' => array(
                                'order' => 1,
                                'order/all' => 1,
                                'order/batch' => 1,
                            ),
                        ),
                        'accountGroup' => array(
                            'get' => array(
                                'cash/balance' => 1,
                                'margin/balance' => 1,
                                'margin/risk' => 1,
                                'futures/collateral-balance' => 1,
                                'futures/position' => 1,
                                'futures/risk' => 1,
                                'futures/funding-payments' => 1,
                                'order/hist' => 1,
                                'spot/fee' => 1,
                            ),
                            'post' => array(
                                'transfer' => 1,
                                'futures/transfer/deposit' => 1,
                                'futures/transfer/withdraw' => 1,
                            ),
                        ),
                    ),
                ),
                'v2' => array(
                    'public' => array(
                        'get' => array(
                            'assets' => 1,
                            'futures/contract' => 1,
                            'futures/collateral' => 1,
                            'futures/pricing-data' => 1,
                            'futures/ticker' => 1,
                            'risk-limit-info' => 1,
                        ),
                    ),
                    'private' => array(
                        'data' => array(
                            'get' => array(
                                'order/hist' => 1,
                            ),
                        ),
                        'get' => array(
                            'account/info' => 1,
                        ),
                        'accountGroup' => array(
                            'get' => array(
                                'order/hist' => 1,
                                'futures/position' => 1,
                                'futures/free-margin' => 1,
                                'futures/order/hist/current' => 1,
                                'futures/funding-payments' => 1,
                                'futures/order/open' => 1,
                                'futures/order/status' => 1,
                            ),
                            'post' => array(
                                'futures/isolated-position-margin' => 1,
                                'futures/margin-type' => 1,
                                'futures/leverage' => 1,
                                'futures/transfer/deposit' => 1,
                                'futures/transfer/withdraw' => 1,
                                'futures/order' => 1,
                                'futures/order/batch' => 1,
                                'futures/order/open' => 1,
                                'subuser/subuser-transfer' => 1,
                                'subuser/subuser-transfer-hist' => 1,
                            ),
                            'delete' => array(
                                'futures/order' => 1,
                                'futures/order/batch' => 1,
                                'futures/order/all' => 1,
                            ),
                        ),
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'feeSide' => 'get',
                    'tierBased' => true,
                    'percentage' => true,
                    'taker' => $this->parse_number('0.002'),
                    'maker' => $this->parse_number('0.002'),
                ),
            ),
            'precisionMode' => TICK_SIZE,
            'options' => array(
                'account-category' => 'cash', // 'cash', 'margin', 'futures' // obsolete
                'account-group' => null,
                'fetchClosedOrders' => array(
                    'method' => 'v2PrivateDataGetOrderHist', // 'v1PrivateAccountCategoryGetOrderHistCurrent'
                ),
                'defaultType' => 'spot', // 'spot', 'margin', 'swap'
                'accountsByType' => array(
                    'spot' => 'cash',
                    'swap' => 'futures',
                    'margin' => 'margin',
                ),
                'transfer' => array(
                    'fillResponseFromRequest' => true,
                ),
                'networks' => array(
                    'BSC' => 'BEP20 ' . '(BSC)',
                    'ARB' => 'arbitrum',
                    'SOL' => 'Solana',
                    'AVAX' => 'avalanche C chain',
                    'OMNI' => 'Omni',
                    // 'TRC' => 'TRC20',
                    'TRC20' => 'TRC20',
                    'ERC20' => 'ERC20',
                    'GO20' => 'GO20',
                    'BEP2' => 'BEP2',
                    'BTC' => 'Bitcoin',
                    'BCH' => 'Bitcoin ABC',
                    'LTC' => 'Litecoin',
                    'MATIC' => 'Matic Network',
                    'AKT' => 'Akash',
                ),
            ),
            'features' => array(
                'default' => array(
                    'sandbox' => true,
                    'createOrder' => array(
                        'marginMode' => true,
                        'triggerPrice' => true,
                        'triggerPriceType' => null,
                        'triggerDirection' => false,
                        'stopLossPrice' => false, // todo with triggerprice
                        'takeProfitPrice' => false, // todo with triggerprice
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => true,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'trailing' => false,
                        'leverage' => false,
                        'marketBuyRequiresPrice' => false,
                        'marketBuyByCost' => false,
                        'selfTradePrevention' => false,
                        'iceberg' => false,
                    ),
                    'createOrders' => array(
                        'max' => 10,
                    ),
                    'fetchMyTrades' => null,
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'marketType' => true,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => null,
                        'trigger' => false,
                        'trailing' => false,
                        'marketType' => true,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => null,
                    'fetchClosedOrders' => null,
                    'fetchOHLCV' => array(
                        'limit' => 500,
                    ),
                ),
                'spot' => array(
                    'extends' => 'default',
                    'fetchClosedOrders' => array(
                        'marginMode' => false,
                        'limit' => 1000,
                        'daysBack' => 100000,
                        'daysBackCanceled' => 1,
                        'untilDays' => 100000,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                ),
                'forDerivatives' => array(
                    'extends' => 'default',
                    'createOrder' => array(
                        // todo => implementation
                        'attachedStopLossTakeProfit' => array(
                            'triggerPriceType' => array(
                                'last' => true,
                                'mark' => false,
                                'index' => false,
                            ),
                            'price' => false,
                        ),
                    ),
                    'fetchClosedOrders' => array(
                        'marginMode' => false,
                        'limit' => 1000,
                        'daysBack' => null,
                        'daysBackCanceled' => null,
                        'untilDays' => null,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                ),
                'swap' => array(
                    'linear' => array(
                        'extends' => 'forDerivatives',
                    ),
                    'inverse' => null,
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
            'exceptions' => array(
                'exact' => array(
                    // not documented
                    '1900' => '\\ccxt\\BadRequest', // array("code":1900,"message":"Invalid Http Request Input")
                    '2100' => '\\ccxt\\AuthenticationError', // array("code":2100,"message":"ApiKeyFailure")
                    '5002' => '\\ccxt\\BadSymbol', // array("code":5002,"message":"Invalid Symbol")
                    '6001' => '\\ccxt\\BadSymbol', // array("code":6001,"message":"Trading is disabled on symbol.")
                    '6010' => '\\ccxt\\InsufficientFunds', // array('code' => 6010, 'message' => 'Not enough balance.')
                    '60060' => '\\ccxt\\InvalidOrder', // array( 'code' => 60060, 'message' => 'The order is already filled or canceled.' )
                    '600503' => '\\ccxt\\InvalidOrder', // array("code":600503,"message":"Notional is too small.")
                    // documented
                    '100001' => '\\ccxt\\BadRequest', // INVALID_HTTP_INPUT Http request is invalid
                    '100002' => '\\ccxt\\BadRequest', // DATA_NOT_AVAILABLE Some required data is missing
                    '100003' => '\\ccxt\\BadRequest', // KEY_CONFLICT The same key exists already
                    '100004' => '\\ccxt\\BadRequest', // INVALID_REQUEST_DATA The HTTP request contains invalid field or argument
                    '100005' => '\\ccxt\\BadRequest', // INVALID_WS_REQUEST_DATA Websocket request contains invalid field or argument
                    '100006' => '\\ccxt\\BadRequest', // INVALID_ARGUMENT The arugment is invalid
                    '100007' => '\\ccxt\\BadRequest', // ENCRYPTION_ERROR Something wrong with data encryption
                    '100008' => '\\ccxt\\BadSymbol', // SYMBOL_ERROR Symbol does not exist or not valid for the request
                    '100009' => '\\ccxt\\AuthenticationError', // AUTHORIZATION_NEEDED Authorization is require for the API access or request
                    '100010' => '\\ccxt\\BadRequest', // INVALID_OPERATION The action is invalid or not allowed for the account
                    '100011' => '\\ccxt\\BadRequest', // INVALID_TIMESTAMP Not a valid timestamp
                    '100012' => '\\ccxt\\BadRequest', // INVALID_STR_FORMAT 'strval' format does not
                    '100013' => '\\ccxt\\BadRequest', // INVALID_NUM_FORMAT Invalid number input
                    '100101' => '\\ccxt\\ExchangeError', // UNKNOWN_ERROR Some unknown error
                    '150001' => '\\ccxt\\BadRequest', // INVALID_JSON_FORMAT Require a valid json object
                    '200001' => '\\ccxt\\AuthenticationError', // AUTHENTICATION_FAILED Authorization failed
                    '200002' => '\\ccxt\\ExchangeError', // TOO_MANY_ATTEMPTS Tried and failed too many times
                    '200003' => '\\ccxt\\ExchangeError', // ACCOUNT_NOT_FOUND Account not exist
                    '200004' => '\\ccxt\\ExchangeError', // ACCOUNT_NOT_SETUP Account not setup properly
                    '200005' => '\\ccxt\\ExchangeError', // ACCOUNT_ALREADY_EXIST Account already exist
                    '200006' => '\\ccxt\\ExchangeError', // ACCOUNT_ERROR Some error related with error
                    '200007' => '\\ccxt\\ExchangeError', // CODE_NOT_FOUND
                    '200008' => '\\ccxt\\ExchangeError', // CODE_EXPIRED Code expired
                    '200009' => '\\ccxt\\ExchangeError', // CODE_MISMATCH Code does not match
                    '200010' => '\\ccxt\\AuthenticationError', // PASSWORD_ERROR Wrong assword
                    '200011' => '\\ccxt\\ExchangeError', // CODE_GEN_FAILED Do not generate required code promptly
                    '200012' => '\\ccxt\\ExchangeError', // FAKE_COKE_VERIFY
                    '200013' => '\\ccxt\\ExchangeError', // SECURITY_ALERT Provide security alert message
                    '200014' => '\\ccxt\\PermissionDenied', // RESTRICTED_ACCOUNT Account is restricted for certain activity, such, or withdraw.
                    '200015' => '\\ccxt\\PermissionDenied', // PERMISSION_DENIED No enough permission for the operation
                    '300001' => '\\ccxt\\InvalidOrder', // INVALID_PRICE Order price is invalid
                    '300002' => '\\ccxt\\InvalidOrder', // INVALID_QTY Order size is invalid
                    '300003' => '\\ccxt\\InvalidOrder', // INVALID_SIDE Order side is invalid
                    '300004' => '\\ccxt\\InvalidOrder', // INVALID_NOTIONAL Notional is too small or too large
                    '300005' => '\\ccxt\\InvalidOrder', // INVALID_TYPE Order typs is invalid
                    '300006' => '\\ccxt\\InvalidOrder', // INVALID_ORDER_ID Order id is invalid
                    '300007' => '\\ccxt\\InvalidOrder', // INVALID_TIME_IN_FORCE Time In Force in order request is invalid
                    '300008' => '\\ccxt\\InvalidOrder', // INVALID_ORDER_PARAMETER Some order parameter is invalid
                    '300009' => '\\ccxt\\InvalidOrder', // TRADING_VIOLATION Trading violation on account or asset
                    '300011' => '\\ccxt\\InsufficientFunds', // INVALID_BALANCE No enough account or asset balance for the trading
                    '300012' => '\\ccxt\\BadSymbol', // INVALID_PRODUCT Not a valid product supported by exchange
                    '300013' => '\\ccxt\\InvalidOrder', // INVALID_BATCH_ORDER Some or all orders are invalid in batch order request
                    '300014' => '\\ccxt\\InvalidOrder', // array("code":300014,"message":"Order price doesn't conform to the required tick size => 0.1","reason":"TICK_SIZE_VIOLATION")
                    '300020' => '\\ccxt\\InvalidOrder', // TRADING_RESTRICTED There is some trading restriction on account or asset
                    '300021' => '\\ccxt\\AccountSuspended', // array("code":300021,"message":"Trading disabled for this account.","reason":"TRADING_DISABLED")
                    '300031' => '\\ccxt\\InvalidOrder', // NO_MARKET_PRICE No market price for market type order trading
                    '310001' => '\\ccxt\\InsufficientFunds', // INVALID_MARGIN_BALANCE No enough margin balance
                    '310002' => '\\ccxt\\InvalidOrder', // INVALID_MARGIN_ACCOUNT Not a valid account for margin trading
                    '310003' => '\\ccxt\\InvalidOrder', // MARGIN_TOO_RISKY Leverage is too high
                    '310004' => '\\ccxt\\BadSymbol', // INVALID_MARGIN_ASSET This asset does not support margin trading
                    '310005' => '\\ccxt\\InvalidOrder', // INVALID_REFERENCE_PRICE There is no valid reference price
                    '510001' => '\\ccxt\\ExchangeError', // SERVER_ERROR Something wrong with server.
                    '900001' => '\\ccxt\\ExchangeError', // HUMAN_CHALLENGE Human change do not pass
                ),
                'broad' => array(),
            ),
            'commonCurrencies' => array(
                'XBT' => 'XBT', // this is not BTC ! just another token
                'BOND' => 'BONDED',
                'BTCBEAR' => 'BEAR',
                'BTCBULL' => 'BULL',
                'BYN' => 'BeyondFi',
                'PLN' => 'Pollen',
            ),
        ));
    }

    public function get_account($params = array ()) {
        // get current or provided bitmax sub-$account
        $account = $this->safe_value($params, 'account', $this->options['account']);
        $lowercaseAccount = strtolower($account);
        return $this->capitalize($lowercaseAccount);
    }

    public function fetch_currencies($params = array ()): ?array {
        /**
         * fetches all available currencies on an exchange
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an associative dictionary of currencies
         */
        $response = $this->v2PublicGetAssets ($params);
        //
        //    {
        //        "code" => "0",
        //        "data" => array(
        //            array(
        //                "assetCode" => "USDT",
        //                "assetName" => "Tether",
        //                "precisionScale" => 9,
        //                "nativeScale" => 4,
        //                "blockChain" => array(
        //                    array(
        //                        "chainName" => "Solana",
        //                        "withdrawFee" => "2.0",
        //                        "allowDeposit" => true,
        //                        "allowWithdraw" => true,
        //                        "minDepositAmt" => "0.01",
        //                        "minWithdrawal" => "4.0",
        //                        "numConfirmations" => 1
        //                    ),
        //                    ...
        //                )
        //            ),
        //         )
        //    }
        //
        $data = $this->safe_list($response, 'data', array());
        $result = array();
        for ($i = 0; $i < count($data); $i++) {
            $currency = $data[$i];
            $id = $this->safe_string($currency, 'assetCode');
            $code = $this->safe_currency_code($id);
            $chains = $this->safe_list($currency, 'blockChain', array());
            $precision = $this->parse_number($this->parse_precision($this->safe_string($currency, 'nativeScale')));
            $networks = array();
            for ($j = 0; $j < count($chains); $j++) {
                $networkEtnry = $chains[$j];
                $networkId = $this->safe_string($networkEtnry, 'chainName');
                $networkCode = $this->network_code_to_id($networkId);
                $networks[$networkCode] = array(
                    'fee' => $this->safe_number($networkEtnry, 'withdrawFee'),
                    'active' => null,
                    'withdraw' => $this->safe_bool($networkEtnry, 'allowWithdraw'),
                    'deposit' => $this->safe_bool($networkEtnry, 'allowDeposit'),
                    'precision' => $precision,
                    'limits' => array(
                        'amount' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'withdraw' => array(
                            'min' => $this->safe_number($networkEtnry, 'minWithdrawal'),
                            'max' => null,
                        ),
                        'deposit' => array(
                            'min' => $this->safe_number($networkEtnry, 'minDepositAmt'),
                            'max' => null,
                        ),
                    ),
                );
            }
            // todo type => if (chainsLength === 0 && (str_ends_with(assetName, ' Staking') || mb_strpos(assetName, ' Reward ') !== false || mb_strpos(assetName, 'Slot Auction') !== false || mb_strpos(assetName, ' Freeze Asset') !== false))
            $result[$code] = $this->safe_currency_structure(array(
                'id' => $id,
                'code' => $code,
                'info' => $currency,
                'type' => null,
                'margin' => null,
                'name' => $this->safe_string($currency, 'assetName'),
                'active' => null,
                'deposit' => null,
                'withdraw' => null,
                'fee' => null,
                'precision' => $precision,
                'limits' => array(
                    'amount' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'withdraw' => array(
                        'min' => $this->safe_number($currency, 'minWithdrawalAmt'),
                        'max' => null,
                    ),
                ),
                'networks' => $networks,
            ));
        }
        return $result;
    }

    public function fetch_markets($params = array ()): array {
        /**
         * retrieves data on all markets for ascendex
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing market data
         */
        $spotPromise = $this->fetch_spot_markets($params);
        $contractPromise = $this->fetch_contract_markets($params);
        list($spotMarkets, $contractMarkets) = array( $spotPromise, $contractPromise );
        return $this->array_concat($spotMarkets, $contractMarkets);
    }

    public function fetch_spot_markets($params = array ()): array {
        $productsPromise = $this->v1PublicGetProducts ($params);
        //
        //     {
        //         "code" => 0,
        //         "data" => array(
        //             array(
        //                 "symbol" => "LBA/BTC",
        //                 "baseAsset" => "LBA",
        //                 "quoteAsset" => "BTC",
        //                 "status" => "Normal",
        //                 "minNotional" => "0.000625",
        //                 "maxNotional" => "6.25",
        //                 "marginTradable" => false,
        //                 "commissionType" => "Quote",
        //                 "commissionReserveRate" => "0.001",
        //                 "tickSize" => "0.000000001",
        //                 "lotSize" => "1"
        //             ),
        //         )
        //     }
        //
        $cashPromise = $this->v1PublicGetCashProducts ($params);
        //
        //     {
        //         "code" => 0,
        //         "data" => array(
        //             {
        //                 "symbol" => "QTUM/BTC",
        //                 "displayName" => "QTUM/BTC",
        //                 "domain" => "BTC",
        //                 "tradingStartTime" => 1569506400000,
        //                 "collapseDecimals" => "0.0001,0.000001,0.00000001",
        //                 "minQty" => "0.000000001",
        //                 "maxQty" => "1000000000",
        //                 "minNotional" => "0.000625",
        //                 "maxNotional" => "12.5",
        //                 "statusCode" => "Normal",
        //                 "statusMessage" => "",
        //                 "tickSize" => "0.00000001",
        //                 "useTick" => false,
        //                 "lotSize" => "0.1",
        //                 "useLot" => false,
        //                 "commissionType" => "Quote",
        //                 "commissionReserveRate" => "0.001",
        //                 "qtyScale" => 1,
        //                 "priceScale" => 8,
        //                 "notionalScale" => 4
        //             }
        //         )
        //     }
        //
        list($products, $cash) = array( $productsPromise, $cashPromise );
        $productsData = $this->safe_list($products, 'data', array());
        $productsById = $this->index_by($productsData, 'symbol');
        $cashData = $this->safe_list($cash, 'data', array());
        $cashAndPerpetualsById = $this->index_by($cashData, 'symbol');
        $dataById = $this->deep_extend($productsById, $cashAndPerpetualsById);
        $ids = is_array($dataById) ? array_keys($dataById) : array();
        $result = array();
        for ($i = 0; $i < count($ids); $i++) {
            $id = $ids[$i];
            if (mb_strpos($id, '-PERP') !== false) {
                continue; // skip perpetuals, endpoint returns them
            }
            $market = $dataById[$id];
            $status = $this->safe_string($market, 'status');
            $domain = $this->safe_string($market, 'domain');
            $active = false;
            if ((($status === 'Normal') || ($status === 'InternalTrading')) && ($domain !== 'LeveragedETF')) {
                $active = true;
            }
            $minQty = $this->safe_number($market, 'minQty');
            $maxQty = $this->safe_number($market, 'maxQty');
            $minPrice = $this->safe_number($market, 'tickSize');
            $maxPrice = null;
            $underlying = $this->safe_string_2($market, 'underlying', 'symbol');
            $parts = explode('/', $underlying);
            $baseId = $this->safe_string($parts, 0);
            $quoteId = $this->safe_string($parts, 1);
            $base = $this->safe_currency_code($baseId);
            $quote = $this->safe_currency_code($quoteId);
            $fee = $this->safe_number($market, 'commissionReserveRate');
            $marginTradable = $this->safe_bool($market, 'marginTradable', false);
            $result[] = array(
                'id' => $id,
                'symbol' => $base . '/' . $quote,
                'base' => $base,
                'baseId' => $baseId,
                'quote' => $quote,
                'quoteId' => $quoteId,
                'settle' => null,
                'settleId' => null,
                'type' => 'spot',
                'spot' => true,
                'margin' => $marginTradable,
                'swap' => false,
                'future' => false,
                'option' => false,
                'active' => $active,
                'contract' => false,
                'linear' => null,
                'inverse' => null,
                'taker' => $fee,
                'maker' => $fee,
                'contractSize' => null,
                'expiry' => null,
                'expiryDatetime' => null,
                'strike' => null,
                'optionType' => null,
                'precision' => array(
                    'amount' => $this->safe_number($market, 'lotSize'),
                    'price' => $this->safe_number($market, 'tickSize'),
                ),
                'limits' => array(
                    'leverage' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'amount' => array(
                        'min' => $minQty,
                        'max' => $maxQty,
                    ),
                    'price' => array(
                        'min' => $minPrice,
                        'max' => $maxPrice,
                    ),
                    'cost' => array(
                        'min' => $this->safe_number($market, 'minNotional'),
                        'max' => $this->safe_number($market, 'maxNotional'),
                    ),
                ),
                'created' => $this->safe_integer($market, 'tradingStartTime'),
                'info' => $market,
            );
        }
        return $result;
    }

    public function fetch_contract_markets($params = array ()): array {
        $contracts = $this->v2PublicGetFuturesContract ($params);
        //
        //    {
        //        "code" => 0,
        //        "data" => array(
        //            {
        //                "symbol" => "BTC-PERP",
        //                "status" => "Normal",
        //                "displayName" => "BTCUSDT",
        //                "settlementAsset" => "USDT",
        //                "underlying" => "BTC/USDT",
        //                "tradingStartTime" => 1579701600000,
        //                "priceFilter" => array(
        //                    "minPrice" => "0.1",
        //                    "maxPrice" => "1000000",
        //                    "tickSize" => "0.1"
        //                ),
        //                "lotSizeFilter" => array(
        //                    "minQty" => "0.0001",
        //                    "maxQty" => "1000000000",
        //                    "lotSize" => "0.0001"
        //                ),
        //                "commissionType" => "Quote",
        //                "commissionReserveRate" => "0.001",
        //                "marketOrderPriceMarkup" => "0.03",
        //                "marginRequirements" => array(
        //                    array(
        //                        "positionNotionalLowerBound" => "0",
        //                        "positionNotionalUpperBound" => "50000",
        //                        "initialMarginRate" => "0.01",
        //                        "maintenanceMarginRate" => "0.006"
        //                    ),
        //                    ...
        //                )
        //            }
        //        )
        //    }
        //
        $data = $this->safe_list($contracts, 'data', array());
        $result = array();
        for ($i = 0; $i < count($data); $i++) {
            $market = $data[$i];
            $id = $this->safe_string($market, 'symbol');
            $underlying = $this->safe_string($market, 'underlying');
            $parts = explode('/', $underlying);
            $baseId = $this->safe_string($parts, 0);
            $base = $this->safe_currency_code($baseId);
            $quoteId = $this->safe_string($parts, 1);
            $quote = $this->safe_currency_code($quoteId);
            $settleId = $this->safe_string($market, 'settlementAsset');
            $settle = $this->safe_currency_code($settleId);
            $linear = $settle === $quote;
            $inverse = $settle === $base;
            $symbol = $base . '/' . $quote . ':' . $settle;
            $priceFilter = $this->safe_dict($market, 'priceFilter');
            $lotSizeFilter = $this->safe_dict($market, 'lotSizeFilter');
            $fee = $this->safe_number($market, 'commissionReserveRate');
            $result[] = array(
                'id' => $id,
                '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_string($market, 'status') === 'Normal',
                'contract' => true,
                'linear' => $linear,
                'inverse' => $inverse,
                'taker' => $fee,
                'maker' => $fee,
                'contractSize' => $this->parse_number('1'),
                'expiry' => null,
                'expiryDatetime' => null,
                'strike' => null,
                'optionType' => null,
                'precision' => array(
                    'amount' => $this->safe_number($lotSizeFilter, 'lotSize'),
                    'price' => $this->safe_number($priceFilter, 'tickSize'),
                ),
                'limits' => array(
                    'leverage' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'amount' => array(
                        'min' => $this->safe_number($lotSizeFilter, 'minQty'),
                        'max' => $this->safe_number($lotSizeFilter, 'maxQty'),
                    ),
                    'price' => array(
                        'min' => $this->safe_number($priceFilter, 'minPrice'),
                        'max' => $this->safe_number($priceFilter, 'maxPrice'),
                    ),
                    'cost' => array(
                        'min' => $this->safe_number($market, 'minNotional'),
                        'max' => $this->safe_number($market, 'maxNotional'),
                    ),
                ),
                'created' => $this->safe_integer($market, 'tradingStartTime'),
                'info' => $market,
            );
        }
        return $result;
    }

    public function fetch_time($params = array ()): ?int {
        /**
         * fetches the current integer timestamp in milliseconds from the ascendex server
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {int} the current integer timestamp in milliseconds from the ascendex server
         */
        $request = array(
            'requestTime' => $this->milliseconds(),
        );
        $response = $this->v1PublicGetExchangeInfo ($this->extend($request, $params));
        //
        //    {
        //        "code" => 0,
        //        "data" => {
        //            "requestTimeEcho" => 1656560463601,
        //            "requestReceiveAt" => 1656560464331,
        //            "latency" => 730
        //        }
        //    }
        //
        $data = $this->safe_dict($response, 'data', array());
        return $this->safe_integer($data, 'requestReceiveAt');
    }

    public function fetch_accounts($params = array ()): array {
        /**
         * fetch all the accounts associated with a profile
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=account-structure account structures~ indexed by the account type
         */
        $accountGroup = $this->safe_string($this->options, 'account-group');
        $response = null;
        if ($accountGroup === null) {
            $response = $this->v1PrivateGetInfo ($params);
            //
            //     {
            //         "code":0,
            //         "data":{
            //             "email":"igor.kroitor@gmail.com",
            //             "accountGroup":8,
            //             "viewPermission":true,
            //             "tradePermission":true,
            //             "transferPermission":true,
            //             "cashAccount":["cshrHKLZCjlZ2ejqkmvIHHtPmLYqdnda"],
            //             "marginAccount":["martXoh1v1N3EMQC5FDtSj5VHso8aI2Z"],
            //             "futuresAccount":["futc9r7UmFJAyBY2rE3beA2JFxav2XFF"],
            //             "userUID":"U6491137460"
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $accountGroup = $this->safe_string($data, 'accountGroup');
            $this->options['account-group'] = $accountGroup;
        }
        return array(
            array(
                'id' => $accountGroup,
                'type' => null,
                'code' => null,
                'info' => $response,
            ),
        );
    }

    public function parse_balance($response): array {
        $result = array(
            'info' => $response,
            'timestamp' => null,
            'datetime' => null,
        );
        $balances = $this->safe_list($response, 'data', array());
        for ($i = 0; $i < count($balances); $i++) {
            $balance = $balances[$i];
            $code = $this->safe_currency_code($this->safe_string($balance, 'asset'));
            $account = $this->account();
            $account['free'] = $this->safe_string($balance, 'availableBalance');
            $account['total'] = $this->safe_string($balance, 'totalBalance');
            $result[$code] = $account;
        }
        return $this->safe_balance($result);
    }

    public function parse_margin_balance($response) {
        $result = array(
            'info' => $response,
            'timestamp' => null,
            'datetime' => null,
        );
        $balances = $this->safe_list($response, 'data', array());
        for ($i = 0; $i < count($balances); $i++) {
            $balance = $balances[$i];
            $code = $this->safe_currency_code($this->safe_string($balance, 'asset'));
            $account = $this->account();
            $account['free'] = $this->safe_string($balance, 'availableBalance');
            $account['total'] = $this->safe_string($balance, 'totalBalance');
            $debt = $this->safe_string($balance, 'borrowed');
            $interest = $this->safe_string($balance, 'interest');
            $account['debt'] = Precise::string_add($debt, $interest);
            $result[$code] = $account;
        }
        return $this->safe_balance($result);
    }

    public function parse_swap_balance($response) {
        $result = array(
            'info' => $response,
            'timestamp' => null,
            'datetime' => null,
        );
        $data = $this->safe_dict($response, 'data', array());
        $collaterals = $this->safe_list($data, 'collaterals', array());
        for ($i = 0; $i < count($collaterals); $i++) {
            $balance = $collaterals[$i];
            $code = $this->safe_currency_code($this->safe_string($balance, 'asset'));
            $account = $this->account();
            $account['total'] = $this->safe_string($balance, 'balance');
            $result[$code] = $account;
        }
        return $this->safe_balance($result);
    }

    public function fetch_balance($params = array ()): array {
        /**
         * query for balance and get the amount of funds available for trading or funds locked in orders
         *
         * @see https://ascendex.github.io/ascendex-pro-api/#cash-$account-balance
         * @see https://ascendex.github.io/ascendex-pro-api/#margin-$account-balance
         * @see https://ascendex.github.io/ascendex-futures-pro-api-v2/#position
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->type] wallet type, 'spot', 'margin', or 'swap'
         * @param {string} [$params->marginMode] 'cross' or null, for spot margin trading, value of 'isolated' is invalid
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
         */
        $this->load_markets();
        $this->load_accounts();
        $marketType = null;
        $marginMode = null;
        list($marketType, $params) = $this->handle_market_type_and_params('fetchBalance', null, $params);
        list($marginMode, $params) = $this->handle_margin_mode_and_params('fetchBalance', $params);
        $isMargin = $this->safe_bool($params, 'margin', false);
        $isCross = $marginMode === 'cross';
        $marketType = ($isMargin || $isCross) ? 'margin' : $marketType;
        $params = $this->omit($params, 'margin');
        $accountsByType = $this->safe_dict($this->options, 'accountsByType', array());
        $accountCategory = $this->safe_string($accountsByType, $marketType, 'cash');
        $account = $this->safe_dict($this->accounts, 0, array());
        $accountGroup = $this->safe_string($account, 'id');
        $request = array(
            'account-group' => $accountGroup,
        );
        if (($marginMode === 'isolated') && ($marketType !== 'swap')) {
            throw new BadRequest($this->id . ' does not supported isolated margin trading');
        }
        if (($accountCategory === 'cash') || ($accountCategory === 'margin')) {
            $request['account-category'] = $accountCategory;
        }
        $response = null;
        if (($marketType === 'spot') || ($marketType === 'margin')) {
            $response = $this->v1PrivateAccountCategoryGetBalance ($this->extend($request, $params));
        } elseif ($marketType === 'swap') {
            $response = $this->v2PrivateAccountGroupGetFuturesPosition ($this->extend($request, $params));
        } else {
            throw new NotSupported($this->id . ' fetchBalance() is not currently supported for ' . $marketType . ' markets');
        }
        //
        // cash
        //
        //     {
        //         "code" => 0,
        //         "data" => array(
        //             array(
        //                 "asset" => "BCHSV",
        //                 "totalBalance" => "64.298000048",
        //                 "availableBalance" => "64.298000048",
        //             ),
        //         )
        //     }
        //
        // margin
        //
        //     {
        //         "code" => 0,
        //         "data" => array(
        //             array(
        //                 "asset" => "BCHSV",
        //                 "totalBalance" => "64.298000048",
        //                 "availableBalance" => "64.298000048",
        //                 "borrowed" => "0",
        //                 "interest" => "0",
        //             ),
        //         )
        //     }
        //
        // swap
        //
        //     {
        //         "code" => 0,
        //         "data" => array(
        //             "accountId" => "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
        //             "ac" => "FUTURES",
        //             "collaterals" => array(
        //                 array("asset":"ADA","balance":"0.355803","referencePrice":"1.05095","discountFactor":"0.9"),
        //                 array("asset":"USDT","balance":"0.000014519","referencePrice":"1","discountFactor":"1")
        //             ),
        //         )j
        //     }
        //
        if ($marketType === 'swap') {
            return $this->parse_swap_balance($response);
        } elseif ($marketType === 'margin') {
            return $this->parse_margin_balance($response);
        } else {
            return $this->parse_balance($response);
        }
    }

    public function fetch_order_book(string $symbol, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other $data
         * @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
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = $this->v1PublicGetDepth ($this->extend($request, $params));
        //
        //     {
        //         "code":0,
        //         "data":{
        //             "m":"depth-snapshot",
        //             "symbol":"BTC-PERP",
        //             "data":{
        //                 "ts":1590223998202,
        //                 "seqnum":115444921,
        //                 "asks":[
        //                     ["9207.5","18.2383"],
        //                     ["9207.75","18.8235"],
        //                     ["9208","10.7873"],
        //                 ],
        //                 "bids":[
        //                     ["9207.25","0.4009"],
        //                     ["9207","0.003"],
        //                     ["9206.5","0.003"],
        //                 ]
        //             }
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        $orderbook = $this->safe_dict($data, 'data', array());
        $timestamp = $this->safe_integer($orderbook, 'ts');
        $result = $this->parse_order_book($orderbook, $symbol, $timestamp);
        $result['nonce'] = $this->safe_integer($orderbook, 'seqnum');
        return $result;
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        //
        //     {
        //         "symbol":"QTUM/BTC",
        //         "open":"0.00016537",
        //         "close":"0.00019077",
        //         "high":"0.000192",
        //         "low":"0.00016537",
        //         "volume":"846.6",
        //         "ask":["0.00018698","26.2"],
        //         "bid":["0.00018408","503.7"],
        //         "type":"spot"
        //     }
        //
        $timestamp = null;
        $marketId = $this->safe_string($ticker, 'symbol');
        $type = $this->safe_string($ticker, 'type');
        $delimiter = ($type === 'spot') ? '/' : null;
        $symbol = $this->safe_symbol($marketId, $market, $delimiter);
        $close = $this->safe_string($ticker, 'close');
        $bid = $this->safe_list($ticker, 'bid', array());
        $ask = $this->safe_list($ticker, 'ask', array());
        $open = $this->safe_string($ticker, 'open');
        return $this->safe_ticker(array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => null,
            'high' => $this->safe_string($ticker, 'high'),
            'low' => $this->safe_string($ticker, 'low'),
            'bid' => $this->safe_string($bid, 0),
            'bidVolume' => $this->safe_string($bid, 1),
            'ask' => $this->safe_string($ask, 0),
            'askVolume' => $this->safe_string($ask, 1),
            'vwap' => null,
            'open' => $open,
            'close' => $close,
            'last' => $close,
            'previousClose' => null, // previous day $close
            'change' => null,
            'percentage' => null,
            'average' => null,
            'baseVolume' => $this->safe_string($ticker, 'volume'),
            'quoteVolume' => null,
            'info' => $ticker,
        ), $market);
    }

    public function fetch_ticker(string $symbol, $params = array ()): array {
        /**
         * fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific $market
         * @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~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = $this->v1PublicGetTicker ($this->extend($request, $params));
        //
        //     {
        //         "code":0,
        //         "data":{
        //             "symbol":"BTC-PERP", // or "BTC/USDT"
        //             "open":"9073",
        //             "close":"9185.75",
        //             "high":"9185.75",
        //             "low":"9185.75",
        //             "volume":"576.8334",
        //             "ask":["9185.75","15.5863"],
        //             "bid":["9185.5","0.003"],
        //             "type":"derivatives", // or "spot"
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        return $this->parse_ticker($data, $market);
    }

    public function fetch_tickers(?array $symbols = null, $params = array ()): array {
        /**
         * fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each $market
         *
         * @see https://ascendex.github.io/ascendex-pro-api/#ticker
         * @see https://ascendex.github.io/ascendex-futures-pro-api-v2/#ticker
         *
         * @param {string[]|null} $symbols unified $symbols of the markets to fetch the ticker for, all $market tickers are returned if not assigned
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structures~
         */
        $this->load_markets();
        $request = array();
        $market = null;
        if ($symbols !== null) {
            $symbol = $this->safe_string($symbols, 0);
            $market = $this->market($symbol);
            $marketIds = $this->market_ids($symbols);
            $request['symbol'] = implode(',', $marketIds);
        }
        $type = null;
        list($type, $params) = $this->handle_market_type_and_params('fetchTickers', $market, $params);
        $response = null;
        if ($type === 'spot') {
            $response = $this->v1PublicGetTicker ($this->extend($request, $params));
        } else {
            $response = $this->v2PublicGetFuturesTicker ($this->extend($request, $params));
        }
        //
        //     {
        //         "code":0,
        //         "data" => {
        //             "symbol":"QTUM/BTC",
        //             "open":"0.00016537",
        //             "close":"0.00019077",
        //             "high":"0.000192",
        //             "low":"0.00016537",
        //             "volume":"846.6",
        //             "ask":["0.00018698","26.2"],
        //             "bid":["0.00018408","503.7"],
        //             "type":"spot"
        //         }
        //     }
        //
        $data = $this->safe_list($response, 'data', array());
        if (gettype($data) !== 'array' || array_keys($data) !== array_keys(array_keys($data))) {
            return $this->parse_tickers(array( $data ), $symbols);
        }
        return $this->parse_tickers($data, $symbols);
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        //
        //     {
        //         "m":"bar",
        //         "s":"BTC/USDT",
        //         "data":{
        //             "i":"1",
        //             "ts":1590228000000,
        //             "o":"9139.59",
        //             "c":"9131.94",
        //             "h":"9139.99",
        //             "l":"9121.71",
        //             "v":"25.20648"
        //         }
        //     }
        //
        $data = $this->safe_dict($ohlcv, 'data', array());
        return array(
            $this->safe_integer($data, 'ts'),
            $this->safe_number($data, 'o'),
            $this->safe_number($data, 'h'),
            $this->safe_number($data, 'l'),
            $this->safe_number($data, 'c'),
            $this->safe_number($data, 'v'),
        );
    }

    public function fetch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches historical candlestick $data containing the open, high, low, and close price, and the volume of a $market
         * @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
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            'interval' => $this->safe_string($this->timeframes, $timeframe, $timeframe),
        );
        // if $since and $limit are not specified
        // the exchange will return just 1 last candle by default
        $duration = $this->parse_timeframe($timeframe);
        $options = $this->safe_dict($this->options, 'fetchOHLCV', array());
        $defaultLimit = $this->safe_integer($options, 'limit', 500);
        $until = $this->safe_integer($params, 'until');
        if ($since !== null) {
            $request['from'] = $since;
            if ($limit === null) {
                $limit = $defaultLimit;
            } else {
                $limit = min ($limit, $defaultLimit);
            }
            $toWithLimit = $this->sum($since, $limit * $duration * 1000, 1);
            if ($until !== null) {
                $request['to'] = min ($toWithLimit, $until + 1);
            } else {
                $request['to'] = $toWithLimit;
            }
        } elseif ($until !== null) {
            $request['to'] = $until + 1;
            if ($limit === null) {
                $limit = $defaultLimit;
            } else {
                $limit = min ($limit, $defaultLimit);
            }
            $request['from'] = $until - ($limit * $duration * 1000);
        } elseif ($limit !== null) {
            $request['n'] = $limit; // max 500
        }
        $params = $this->omit($params, 'until');
        $response = $this->v1PublicGetBarhist ($this->extend($request, $params));
        //
        //     {
        //         "code":0,
        //         "data":array(
        //             {
        //                 "m":"bar",
        //                 "s":"BTC/USDT",
        //                 "data":{
        //                     "i":"1",
        //                     "ts":1590228000000,
        //                     "o":"9139.59",
        //                     "c":"9131.94",
        //                     "h":"9139.99",
        //                     "l":"9121.71",
        //                     "v":"25.20648"
        //                 }
        //             }
        //         )
        //     }
        //
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_ohlcvs($data, $market, $timeframe, $since, $limit);
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // public fetchTrades
        //
        //     {
        //         "p":"9128.5", // price
        //         "q":"0.0030", // quantity
        //         "ts":1590229002385, // $timestamp
        //         "bm":false, // if true, the buyer is the $market maker, we only use this field to "define the $side" of a public $trade
        //         "seqnum":180143985289898554
        //     }
        //
        $timestamp = $this->safe_integer($trade, 'ts');
        $priceString = $this->safe_string_2($trade, 'price', 'p');
        $amountString = $this->safe_string($trade, 'q');
        $buyerIsMaker = $this->safe_bool($trade, 'bm', false);
        $side = $buyerIsMaker ? 'sell' : 'buy';
        $market = $this->safe_market(null, $market);
        return $this->safe_trade(array(
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $market['symbol'],
            'id' => null,
            'order' => null,
            'type' => null,
            'takerOrMaker' => null,
            'side' => $side,
            'price' => $priceString,
            'amount' => $amountString,
            'cost' => null,
            'fee' => null,
        ), $market);
    }

    public function fetch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * get the list of most recent $trades for a particular $symbol
         *
         * @see https://ascendex.github.io/ascendex-pro-api/#$market-$trades
         *
         * @param {string} $symbol unified $symbol of the $market to fetch $trades for
         * @param {int} [$since] timestamp in ms of the earliest trade to fetch
         * @param {int} [$limit] the maximum amount of $trades to fetch
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-$trades trade structures~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        if ($limit !== null) {
            $request['n'] = $limit; // max 100
        }
        $response = $this->v1PublicGetTrades ($this->extend($request, $params));
        //
        //     {
        //         "code":0,
        //         "data":{
        //             "m":"trades",
        //             "symbol":"BTC-PERP",
        //             "data":array(
        //                 array("p":"9128.5","q":"0.0030","ts":1590229002385,"bm":false,"seqnum":180143985289898554),
        //                 array("p":"9129","q":"0.0030","ts":1590229002642,"bm":false,"seqnum":180143985289898587),
        //                 array("p":"9129.5","q":"0.0030","ts":1590229021306,"bm":false,"seqnum":180143985289899043)
        //             )
        //         }
        //     }
        //
        $records = $this->safe_dict($response, 'data', array());
        $trades = $this->safe_list($records, 'data', array());
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function parse_order_status(?string $status) {
        $statuses = array(
            'PendingNew' => 'open',
            'New' => 'open',
            'PartiallyFilled' => 'open',
            'Filled' => 'closed',
            'Canceled' => 'canceled',
            'Rejected' => 'rejected',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        // createOrder
        //
        //     {
        //         "id" => "16e607e2b83a8bXHbAwwoqDo55c166fa",
        //         "orderId" => "16e85b4d9b9a8bXHbAwwoqDoc3d66830",
        //         "orderType" => "Market",
        //         "symbol" => "BTC/USDT",
        //         "timestamp" => 1573576916201
        //     }
        //
        //  & linear (fetchClosedOrders)
        //
        //     {
        //         "ac" => "FUTURES",
        //         "accountId" => "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
        //         "time" => 1640819389454,
        //         "orderId" => "a17e0874ecbdU0711043490bbtcpDU5X",
        //         "seqNum" => -1,
        //         "orderType" => "Limit",
        //         "execInst" => "NULL_VAL", // NULL_VAL, ReduceOnly , ...
        //         "side" => "Buy",
        //         "symbol" => "BTC-PERP",
        //         "price" => "30000",
        //         "orderQty" => "0.002",
        //         "stopPrice" => "0",
        //         "stopBy" => "ref-px",
        //         "status" => "Ack",
        //         "lastExecTime" => 1640819389454,
        //         "lastQty" => "0",
        //         "lastPx" => "0",
        //         "avgFilledPx" => "0",
        //         "cumFilledQty" => "0",
        //         "fee" => "0",
        //         "cumFee" => "0",
        //         "feeAsset" => "",
        //         "errorCode" => "",
        //         "posStopLossPrice" => "0",
        //         "posStopLossTrigger" => "market",
        //         "posTakeProfitPrice" => "0",
        //         "posTakeProfitTrigger" => "market",
        //         "liquidityInd" => "n"
        //      }
        //
        // fetchOrder, fetchOpenOrders, fetchClosedOrders
        //
        //     {
        //         "symbol" =>       "BTC/USDT",
        //         "price" =>        "8131.22",
        //         "orderQty" =>     "0.00082",
        //         "orderType" =>    "Market",
        //         "avgPx" =>        "7392.02",
        //         "cumFee" =>       "0.005152238",
        //         "cumFilledQty" => "0.00082",
        //         "errorCode" =>    "",
        //         "feeAsset" =>     "USDT",
        //         "lastExecTime" => 1575953151764,
        //         "orderId" =>      "a16eee20b6750866943712zWEDdAjt3",
        //         "seqNum" =>       2623469,
        //         "side" =>         "Buy",
        //         "status" =>       "Filled",
        //         "stopPrice" =>    "",
        //         "execInst" =>     "NULL_VAL" // "Post" (for $postOnly orders), "reduceOnly" (for $reduceOnly orders)
        //     }
        //
        //     {
        //         "orderId" => "a173ad938fc3U22666567717788c3b66",   // orderId
        //         "seqNum" => 18777366360,                           // sequence number
        //         "accountId" => "cshwSjbpPjSwHmxPdz2CPQVU9mnbzPpt", // accountId
        //         "symbol" => "BTC/USDT",                            // $symbol
        //         "orderType" => "Limit",                            // $order $type (Limit/Market/StopMarket/StopLimit)
        //         "side" => "Sell",                                  // $order $side (Buy/Sell)
        //         "price" => "11346.77",                             // $order $price
        //         "stopPrice" => "0",                                // stop $price (0 by default)
        //         "orderQty" => "0.01",                              // $order quantity (in base asset)
        //         "status" => "Canceled",                            // $order $status (Filled/Canceled/Rejected)
        //         "createTime" => 1596344995793,                     // $order creation time
        //         "lastExecTime" => 1596344996053,                   // last execution time
        //         "avgFillPrice" => "11346.77",                      // $average $filled $price
        //         "fillQty" => "0.01",                               // $filled quantity (in base asset)
        //         "fee" => "-0.004992579",                           // cummulative $fee-> if negative, this value is the commission charged; if possitive, this value is the rebate received.
        //         "feeAsset" => "USDT"                               // $fee asset
        //     }
        //
        //     array(
        //         "ac" => "FUTURES",
        //         "accountId" => "testabcdefg",
        //         "avgPx" => "0",
        //         "cumFee" => "0",
        //         "cumQty" => "0",
        //         "errorCode" => "NULL_VAL",
        //         "execInst" => "NULL_VAL",
        //         "feeAsset" => "USDT",
        //         "lastExecTime" => 1584072844085,
        //         "orderId" => "r170d21956dd5450276356bbtcpKa74",
        //         "orderQty" => "1.1499",
        //         "orderType" => "Limit",
        //         "price" => "4000",
        //         "sendingTime" => 1584072841033,
        //         "seqNum" => 24105338,
        //         "side" => "Buy",
        //         "status" => "Canceled",
        //         "stopPrice" => "",
        //         "symbol" => "BTC-PERP"
        //     ),
        //
        $status = $this->parse_order_status($this->safe_string($order, 'status'));
        $marketId = $this->safe_string($order, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market, '/');
        $timestamp = $this->safe_integer_n($order, array( 'timestamp', 'sendingTime', 'time' ));
        $lastTradeTimestamp = $this->safe_integer($order, 'lastExecTime');
        if ($timestamp === null) {
            $timestamp = $lastTradeTimestamp;
        }
        $price = $this->safe_string($order, 'price');
        $amount = $this->safe_string($order, 'orderQty');
        $average = $this->safe_string_2($order, 'avgPx', 'avgFilledPx');
        $filled = $this->safe_string_n($order, array( 'cumFilledQty', 'cumQty', 'fillQty' ));
        $id = $this->safe_string($order, 'orderId');
        $clientOrderId = $this->safe_string($order, 'id');
        if ($clientOrderId !== null) {
            if (strlen($clientOrderId) < 1) {
                $clientOrderId = null;
            }
        }
        $rawTypeLower = $this->safe_string_lower($order, 'orderType');
        $type = $rawTypeLower;
        if ($rawTypeLower !== null) {
            if ($rawTypeLower === 'stoplimit') {
                $type = 'limit';
            }
            if ($rawTypeLower === 'stopmarket') {
                $type = 'market';
            }
        }
        $side = $this->safe_string_lower($order, 'side');
        $feeCost = $this->safe_number_2($order, 'cumFee', 'fee');
        $fee = null;
        if ($feeCost !== null) {
            $feeCurrencyId = $this->safe_string($order, 'feeAsset');
            $feeCurrencyCode = $this->safe_currency_code($feeCurrencyId);
            $fee = array(
                'cost' => $feeCost,
                'currency' => $feeCurrencyCode,
            );
        }
        $triggerPrice = $this->omit_zero($this->safe_string($order, 'stopPrice'));
        $reduceOnly = null;
        $execInst = $this->safe_string_lower($order, 'execInst');
        if ($execInst === 'reduceonly') {
            $reduceOnly = true;
        }
        $postOnly = null;
        if ($execInst === 'post') {
            $postOnly = true;
        }
        return $this->safe_order(array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => $clientOrderId,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => $lastTradeTimestamp,
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => null,
            'postOnly' => $postOnly,
            'reduceOnly' => $reduceOnly,
            'side' => $side,
            'price' => $price,
            'triggerPrice' => $triggerPrice,
            'amount' => $amount,
            'cost' => null,
            'average' => $average,
            'filled' => $filled,
            'remaining' => null,
            'status' => $status,
            'fee' => $fee,
            'trades' => null,
        ), $market);
    }

    public function fetch_trading_fees($params = array ()): array {
        /**
         * fetch the trading $fees for multiple markets
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=$fee-structure $fee structures~ indexed by market symbols
         */
        $this->load_markets();
        $this->load_accounts();
        $account = $this->safe_dict($this->accounts, 0, array());
        $accountGroup = $this->safe_string($account, 'id');
        $request = array(
            'account-group' => $accountGroup,
        );
        $response = $this->v1PrivateAccountGroupGetSpotFee ($this->extend($request, $params));
        //
        //      {
        //         "code" => "0",
        //         "data" => {
        //           "domain" => "spot",
        //           "userUID" => "U1479576457",
        //           "vipLevel" => "0",
        //           "fees" => array(
        //             array( $symbol => 'HT/USDT', $fee => array( taker => '0.001', maker => "0.001" ) ),
        //             array( $symbol => 'LAMB/BTC', $fee => array( taker => '0.002', maker => "0.002" ) ),
        //             array( $symbol => 'STOS/USDT', $fee => array( taker => '0.002', maker => "0.002" ) ),
        //             ...
        //           )
        //         }
        //      }
        //
        $data = $this->safe_dict($response, 'data', array());
        $fees = $this->safe_list($data, 'fees', array());
        $result = array();
        for ($i = 0; $i < count($fees); $i++) {
            $fee = $fees[$i];
            $marketId = $this->safe_string($fee, 'symbol');
            $symbol = $this->safe_symbol($marketId, null, '/');
            $takerMaker = $this->safe_dict($fee, 'fee', array());
            $result[$symbol] = array(
                'info' => $fee,
                'symbol' => $symbol,
                'maker' => $this->safe_number($takerMaker, 'maker'),
                'taker' => $this->safe_number($takerMaker, 'taker'),
                'percentage' => null,
                'tierBased' => null,
            );
        }
        return $result;
    }

    public function create_order_request(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        /**
         * @ignore
         * helper function to build $request
         * @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 you want to trade in units of the base currency
         * @param {float} [$price] the $price at which the order is to be fulfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->timeInForce] "GTC", "IOC", "FOK", or "PO"
         * @param {bool} [$params->postOnly] true or false
         * @param {float} [$params->triggerPrice] the $price at which a trigger order is triggered at
         * @return {array} $request to be sent to the exchange
         */
        $market = $this->market($symbol);
        $marginMode = null;
        $marketType = null;
        list($marginMode, $params) = $this->handle_margin_mode_and_params('createOrderRequest', $params);
        list($marketType, $params) = $this->handle_market_type_and_params('createOrderRequest', $market, $params);
        $accountsByType = $this->safe_dict($this->options, 'accountsByType', array());
        $accountCategory = $this->safe_string($accountsByType, $marketType, 'cash');
        if ($marginMode !== null) {
            $accountCategory = 'margin';
        }
        $account = $this->safe_dict($this->accounts, 0, array());
        $accountGroup = $this->safe_string($account, 'id');
        $clientOrderId = $this->safe_string_2($params, 'clientOrderId', 'id');
        $request = array(
            'account-group' => $accountGroup,
            'account-category' => $accountCategory,
            'symbol' => $market['id'],
            'time' => $this->milliseconds(),
            'orderQty' => $this->amount_to_precision($symbol, $amount),
            'orderType' => $type, // limit, $market, stop_market, stop_limit
            'side' => $side, // buy or sell,
            // 'execInst' => // Post for $postOnly, ReduceOnly for $reduceOnly
            // 'respInst' => 'ACK', // ACK, 'ACCEPT, DONE
        );
        $isMarketOrder = (($type === 'market') || ($type === 'stop_market'));
        $isLimitOrder = (($type === 'limit') || ($type === 'stop_limit'));
        $timeInForce = $this->safe_string($params, 'timeInForce');
        $postOnly = $this->is_post_only($isMarketOrder, false, $params);
        $reduceOnly = $this->safe_bool($params, 'reduceOnly', false);
        $triggerPrice = $this->safe_string_2($params, 'triggerPrice', 'stopPrice');
        if ($isLimitOrder) {
            $request['orderPrice'] = $this->price_to_precision($symbol, $price);
        }
        if ($timeInForce === 'IOC') {
            $request['timeInForce'] = 'IOC';
        }
        if ($timeInForce === 'FOK') {
            $request['timeInForce'] = 'FOK';
        }
        if ($postOnly) {
            $request['postOnly'] = true;
        }
        if ($triggerPrice !== null) {
            $request['stopPrice'] = $this->price_to_precision($symbol, $triggerPrice);
            if ($isLimitOrder) {
                $request['orderType'] = 'stop_limit';
            } elseif ($isMarketOrder) {
                $request['orderType'] = 'stop_market';
            }
        }
        if ($clientOrderId !== null) {
            $request['id'] = $clientOrderId;
        }
        if ($market['spot']) {
            if ($accountCategory !== null) {
                $request['category'] = $accountCategory;
            }
        } else {
            $request['account-category'] = $accountCategory;
            if ($reduceOnly) {
                $request['execInst'] = 'ReduceOnly';
            }
            if ($postOnly) {
                $request['execInst'] = 'Post';
            }
        }
        $params = $this->omit($params, array( 'reduceOnly', 'triggerPrice' ));
        return $this->extend($request, $params);
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        /**
         * create a trade $order on the exchange
         *
         * @see https://ascendex.github.io/ascendex-pro-api/#place-$order
         * @see https://ascendex.github.io/ascendex-futures-pro-api-v2/#new-$order
         *
         * @param {string} $symbol unified CCXT $market $symbol
         * @param {string} $type "limit" or "market"
         * @param {string} $side "buy" or "sell"
         * @param {float} $amount the $amount of currency to trade
         * @param {float} [$price] the $price at which the $order is to be fulfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->timeInForce] "GTC", "IOC", "FOK", or "PO"
         * @param {bool} [$params->postOnly] true or false
         * @param {float} [$params->triggerPrice] the $price at which a trigger $order is triggered at
         * @param {array} [$params->takeProfit] *takeProfit object in $params* containing the triggerPrice that the attached take profit $order will be triggered (perpetual swap markets only)
         * @param {float} [$params->takeProfit.triggerPrice] *swap only* take profit trigger $price
         * @param {array} [$params->stopLoss] *stopLoss object in $params* containing the triggerPrice that the attached stop loss $order will be triggered (perpetual swap markets only)
         * @param {float} [$params->stopLoss.triggerPrice] *swap only* stop loss trigger $price
         * @return ~@link https://docs.ccxt.com/#/?id=$order-structure An $order structure~
         */
        $this->load_markets();
        $this->load_accounts();
        $market = $this->market($symbol);
        $request = $this->create_order_request($symbol, $type, $side, $amount, $price, $params);
        $response = null;
        if ($market['swap']) {
            $response = $this->v2PrivateAccountGroupPostFuturesOrder ($request);
        } else {
            $response = $this->v1PrivateAccountCategoryPostOrder ($request);
        }
        //
        // spot
        //
        //      {
        //          "code":0,
        //          "data" => {
        //              "accountId":"cshwT8RKojkT1HoaA5UdeimR2SrmHG2I",
        //              "ac":"CASH",
        //              "action":"place-$order",
        //              "status":"Ack",
        //              "info" => {
        //                  "symbol":"TRX/USDT",
        //                  "orderType":"StopLimit",
        //                  "timestamp":1654290662172,
        //                  "id":"",
        //                  "orderId":"a1812b6840ddU8191168955av0k6Eyhj"
        //              }
        //          }
        //      }
        //
        // swap
        //
        //      {
        //          "code":0,
        //          "data" => {
        //              "meta" => array(
        //                  "id":"",
        //                  "action":"place-$order",
        //                  "respInst":"ACK"
        //              ),
        //              "order" => {
        //                  "ac":"FUTURES",
        //                  "accountId":"futwT8RKojkT1HoaA5UdeimR2SrmHG2I",
        //                  "time":1654290969965,
        //                  "orderId":"a1812b6cf322U8191168955oJamfTh7b",
        //                  "seqNum":-1,
        //                  "orderType":"StopLimit",
        //                  "execInst":"NULL_VAL",
        //                  "side":"Buy",
        //                  "symbol":"TRX-PERP",
        //                  "price":"0.083",
        //                  "orderQty":"1",
        //                  "stopPrice":"0.082",
        //                  "stopBy":"ref-px",
        //                  "status":"Ack",
        //                  "lastExecTime":1654290969965,
        //                  "lastQty":"0",
        //                  "lastPx":"0",
        //                  "avgFilledPx":"0",
        //                  "cumFilledQty":"0",
        //                  "fee":"0",
        //                  "cumFee":"0",
        //                  "feeAsset":"",
        //                  "errorCode":"",
        //                  "posStopLossPrice":"0",
        //                  "posStopLossTrigger":"market",
        //                  "posTakeProfitPrice":"0",
        //                  "posTakeProfitTrigger":"market",
        //                  "liquidityInd":"n"
        //              }
        //          }
        //      }
        //
        $data = $this->safe_dict($response, 'data', array());
        $order = $this->safe_dict_2($data, 'order', 'info', array());
        return $this->parse_order($order, $market);
    }

    public function create_orders(array $orders, $params = array ()) {
        /**
         * create a list of trade $orders
         *
         * @see https://ascendex.github.io/ascendex-pro-api/#place-batch-$orders
         * @see https://ascendex.github.io/ascendex-futures-pro-api-v2/#place-batch-$orders
         *
         * @param {Array} $orders list of $orders to create, each object should contain the parameters required by createOrder, namely $symbol, $type, $side, $amount, $price and $params
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->timeInForce] "GTC", "IOC", "FOK", or "PO"
         * @param {bool} [$params->postOnly] true or false
         * @param {float} [$params->triggerPrice] the $price at which a trigger order is triggered at
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        $this->load_markets();
        $this->load_accounts();
        $ordersRequests = array();
        $symbol = null;
        $marginMode = null;
        for ($i = 0; $i < count($orders); $i++) {
            $rawOrder = $orders[$i];
            $marketId = $this->safe_string($rawOrder, 'symbol');
            if ($symbol === null) {
                $symbol = $marketId;
            } else {
                if ($symbol !== $marketId) {
                    throw new BadRequest($this->id . ' createOrders() requires all $orders to have the same symbol');
                }
            }
            $type = $this->safe_string($rawOrder, 'type');
            $side = $this->safe_string($rawOrder, 'side');
            $amount = $this->safe_number($rawOrder, 'amount');
            $price = $this->safe_number($rawOrder, 'price');
            $orderParams = $this->safe_dict($rawOrder, 'params', array());
            $marginResult = $this->handle_margin_mode_and_params('createOrders', $orderParams);
            $currentMarginMode = $marginResult[0];
            if ($currentMarginMode !== null) {
                if ($marginMode === null) {
                    $marginMode = $currentMarginMode;
                } else {
                    if ($marginMode !== $currentMarginMode) {
                        throw new BadRequest($this->id . ' createOrders() requires all $orders to have the same margin mode (isolated or cross)');
                    }
                }
            }
            $orderRequest = $this->create_order_request($marketId, $type, $side, $amount, $price, $orderParams);
            $ordersRequests[] = $orderRequest;
        }
        $market = $this->market($symbol);
        $accountsByType = $this->safe_dict($this->options, 'accountsByType', array());
        $accountCategory = $this->safe_string($accountsByType, $market['type'], 'cash');
        if ($marginMode !== null) {
            $accountCategory = 'margin';
        }
        $account = $this->safe_dict($this->accounts, 0, array());
        $accountGroup = $this->safe_string($account, 'id');
        $request = array();
        $response = null;
        if ($market['swap']) {
            throw new NotSupported($this->id . ' createOrders() is not currently supported for swap markets on ascendex');
            // $request['account-group'] = $accountGroup;
            // $request['category'] = $accountCategory;
            // $request['orders'] = $ordersRequests;
            // $response = $this->v2PrivateAccountGroupPostFuturesOrderBatch ($request);
        } else {
            $request['account-group'] = $accountGroup;
            $request['account-category'] = $accountCategory;
            $request['orders'] = $ordersRequests;
            $response = $this->v1PrivateAccountCategoryPostOrderBatch ($request);
        }
        //
        // spot
        //
        //     {
        //         "code" => 0,
        //         "data" => {
        //             "accountId" => "cshdAKBO43TKIh2kJtq7FVVb42KIePyS",
        //             "ac" => "CASH",
        //             "action" => "batch-place-order",
        //             "status" => "Ack",
        //             "info" => array(
        //                 {
        //                     "symbol" => "BTC/USDT",
        //                     "orderType" => "Limit",
        //                     "timestamp" => 1699326589344,
        //                     "id" => "",
        //                     "orderId" => "a18ba7c1f6efU0711043490p3HvjjN5x"
        //                 }
        //             )
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        $info = $this->safe_list($data, 'info', array());
        return $this->parse_orders($info, $market);
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * fetches information on an order made by the user
         *
         * @see https://ascendex.github.io/ascendex-pro-api/#$query-order
         * @see https://ascendex.github.io/ascendex-futures-pro-api-v2/#$query-order-by-$id
         *
         * @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
         * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $this->load_markets();
        $this->load_accounts();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        list($type, $query) = $this->handle_market_type_and_params('fetchOrder', $market, $params);
        $accountsByType = $this->safe_dict($this->options, 'accountsByType', array());
        $accountCategory = $this->safe_string($accountsByType, $type, 'cash');
        $account = $this->safe_dict($this->accounts, 0, array());
        $accountGroup = $this->safe_string($account, 'id');
        $request = array(
            'account-group' => $accountGroup,
            'account-category' => $accountCategory,
            'orderId' => $id,
        );
        $response = null;
        if (($type === 'spot') || ($type === 'margin')) {
            $response = $this->v1PrivateAccountCategoryGetOrderStatus ($this->extend($request, $query));
        } elseif ($type === 'swap') {
            $request['account-category'] = $accountCategory;
            $response = $this->v2PrivateAccountGroupGetFuturesOrderStatus ($this->extend($request, $query));
        } else {
            throw new NotSupported($this->id . ' fetchOrder() is not currently supported for ' . $type . ' markets');
        }
        //
        // AccountCategoryGetOrderStatus
        //
        //     {
        //         "code" => 0,
        //         "accountCategory" => "CASH",
        //         "accountId" => "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo",
        //         "data" => array(
        //             {
        //                 "symbol" =>       "BTC/USDT",
        //                 "price" =>        "8131.22",
        //                 "orderQty" =>     "0.00082",
        //                 "orderType" =>    "Market",
        //                 "avgPx" =>        "7392.02",
        //                 "cumFee" =>       "0.005152238",
        //                 "cumFilledQty" => "0.00082",
        //                 "errorCode" =>    "",
        //                 "feeAsset" =>     "USDT",
        //                 "lastExecTime" => 1575953151764,
        //                 "orderId" =>      "a16eee20b6750866943712zWEDdAjt3",
        //                 "seqNum" =>       2623469,
        //                 "side" =>         "Buy",
        //                 "status" =>       "Filled",
        //                 "stopPrice" =>    "",
        //                 "execInst" =>     "NULL_VAL"
        //             }
        //         )
        //     }
        //
        // AccountGroupGetFuturesOrderStatus
        //
        //     {
        //         "code" => 0,
        //         "accountId" => "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
        //         "ac" => "FUTURES",
        //         "data" => {
        //             "ac" => "FUTURES",
        //             "accountId" => "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
        //             "time" => 1640247020217,
        //             "orderId" => "r17de65747aeU0711043490bbtcp0cmt",
        //             "seqNum" => 28796162908,
        //             "orderType" => "Limit",
        //             "execInst" => "NULL_VAL",
        //             "side" => "Buy",
        //             "symbol" => "BTC-PERP",
        //             "price" => "30000",
        //             "orderQty" => "0.0021",
        //             "stopPrice" => "0",
        //             "stopBy" => "market",
        //             "status" => "New",
        //             "lastExecTime" => 1640247020232,
        //             "lastQty" => "0",
        //             "lastPx" => "0",
        //             "avgFilledPx" => "0",
        //             "cumFilledQty" => "0",
        //             "fee" => "0",
        //             "cumFee" => "0",
        //             "feeAsset" => "USDT",
        //             "errorCode" => "",
        //             "posStopLossPrice" => "0",
        //             "posStopLossTrigger" => "market",
        //             "posTakeProfitPrice" => "0",
        //             "posTakeProfitTrigger" => "market",
        //             "liquidityInd" => "n"
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        return $this->parse_order($data, $market);
    }

    public function fetch_open_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all unfilled currently open $orders
         *
         * @see https://ascendex.github.io/ascendex-pro-api/#list-open-$orders
         * @see https://ascendex.github.io/ascendex-futures-pro-api-v2/#list-open-$orders
         *
         * @param {string} $symbol unified $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch open $orders for
         * @param {int} [$limit] the maximum number of  open $orders structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=$order-structure $order structures~
         */
        $this->load_markets();
        $this->load_accounts();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $symbol = $market['symbol'];
        }
        $account = $this->safe_dict($this->accounts, 0, array());
        $accountGroup = $this->safe_string($account, 'id');
        list($type, $query) = $this->handle_market_type_and_params('fetchOpenOrders', $market, $params);
        $accountsByType = $this->safe_dict($this->options, 'accountsByType', array());
        $accountCategory = $this->safe_string($accountsByType, $type, 'cash');
        $request = array(
            'account-group' => $accountGroup,
            'account-category' => $accountCategory,
        );
        $response = null;
        if (($type === 'spot') || ($type === 'margin')) {
            $response = $this->v1PrivateAccountCategoryGetOrderOpen ($this->extend($request, $query));
        } elseif ($type === 'swap') {
            $request['account-category'] = $accountCategory;
            $response = $this->v2PrivateAccountGroupGetFuturesOrderOpen ($this->extend($request, $query));
        } else {
            throw new NotSupported($this->id . ' fetchOpenOrders() is not currently supported for ' . $type . ' markets');
        }
        //
        // AccountCategoryGetOrderOpen
        //
        //     {
        //         "ac" => "CASH",
        //         "accountId" => "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo",
        //         "code" => 0,
        //         "data" => array(
        //             array(
        //                 "avgPx" => "0",        // Average filled price of the $order
        //                 "cumFee" => "0",       // cumulative fee paid for this $order
        //                 "cumFilledQty" => "0", // cumulative filled quantity
        //                 "errorCode" => "",     // error code; could be empty
        //                 "feeAsset" => "USDT",  // fee asset
        //                 "lastExecTime" => 1576019723550, //  The last execution time of the $order
        //                 "orderId" => "s16ef21882ea0866943712034f36d83", // server provided orderId
        //                 "orderQty" => "0.0083",  // $order quantity
        //                 "orderType" => "Limit",  // $order $type
        //                 "price" => "7105",       // $order price
        //                 "seqNum" => 8193258,     // sequence number
        //                 "side" => "Buy",         // $order side
        //                 "status" => "New",       // $order status on matching engine
        //                 "stopPrice" => "",       // only available for stop $market and stop $limit $orders; otherwise empty
        //                 "symbol" => "BTC/USDT",
        //                 "execInst" => "NULL_VAL" // execution instruction
        //             ),
        //         )
        //     }
        //
        // AccountGroupGetFuturesOrderOpen
        //
        // {
        //     "code" => 0,
        //     "data" => array(
        //         {
        //             "ac" => "FUTURES",
        //             "accountId" => "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
        //             "time" => 1640247020217,
        //             "orderId" => "r17de65747aeU0711043490bbtcp0cmt",
        //             "seqNum" => 28796162908,
        //             "orderType" => "Limit",
        //             "execInst" => "NULL_VAL",
        //             "side" => "Buy",
        //             "symbol" => "BTC-PERP",
        //             "price" => "30000",
        //             "orderQty" => "0.0021",
        //             "stopPrice" => "0",
        //             "stopBy" => "market",
        //             "status" => "New",
        //             "lastExecTime" => 1640247020232,
        //             "lastQty" => "0",
        //             "lastPx" => "0",
        //             "avgFilledPx" => "0",
        //             "cumFilledQty" => "0",
        //             "fee" => "0",
        //             "cumFee" => "0",
        //             "feeAsset" => "USDT",
        //             "errorCode" => "",
        //             "posStopLossPrice" => "0",
        //             "posStopLossTrigger" => "market",
        //             "posTakeProfitPrice" => "0",
        //             "posTakeProfitTrigger" => "market",
        //             "liquidityInd" => "n"
        //         }
        //     )
        // }
        //
        $data = $this->safe_list($response, 'data', array());
        if ($accountCategory === 'futures') {
            return $this->parse_orders($data, $market, $since, $limit);
        }
        // a workaround for https://github.com/ccxt/ccxt/issues/7187
        $orders = array();
        for ($i = 0; $i < count($data); $i++) {
            $order = $this->parse_order($data[$i], $market);
            $orders[] = $order;
        }
        return $this->filter_by_symbol_since_limit($orders, $symbol, $since, $limit);
    }

    public function fetch_closed_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on multiple closed orders made by the user
         *
         * @see https://ascendex.github.io/ascendex-pro-api/#list-history-orders-v2
         * @see https://ascendex.github.io/ascendex-futures-pro-api-v2/#list-current-history-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
         * @param {int} [$params->until] the latest time in ms to fetch orders for
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        $this->load_accounts();
        $account = $this->safe_dict($this->accounts, 0, array());
        $accountGroup = $this->safe_string($account, 'id');
        $request = array(
            // 'category' => $accountCategory,
            // 'symbol' => $market['id'],
            // 'orderType' => 'market', // optional, string
            // 'side' => 'buy', // or 'sell', optional, case insensitive.
            // 'status' => 'Filled', // "Filled", "Canceled", or "Rejected"
            // 'startTime' => exchange.milliseconds (),
            // 'endTime' => exchange.milliseconds (),
            // 'page' => 1,
            // 'pageSize' => 100,
        );
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        list($type, $query) = $this->handle_market_type_and_params('fetchClosedOrders', $market, $params);
        $options = $this->safe_dict($this->options, 'fetchClosedOrders', array());
        $defaultMethod = $this->safe_string($options, 'method', 'v2PrivateDataGetOrderHist');
        $method = $this->get_supported_mapping($type, array(
            'spot' => $defaultMethod,
            'margin' => $defaultMethod,
            'swap' => 'v2PrivateAccountGroupGetFuturesOrderHistCurrent',
        ));
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        $until = $this->safe_string($params, 'until');
        if ($until !== null) {
            $request['endTime'] = $until;
        }
        $accountsByType = $this->safe_dict($this->options, 'accountsByType', array());
        $accountCategory = $this->safe_string($accountsByType, $type, 'cash'); // margin, futures
        $response = null;
        if ($method === 'v1PrivateAccountCategoryGetOrderHistCurrent') {
            $request['account-group'] = $accountGroup;
            $request['account-category'] = $accountCategory;
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $response = $this->v1PrivateAccountCategoryGetOrderHistCurrent ($this->extend($request, $query));
        } elseif ($method === 'v2PrivateDataGetOrderHist') {
            $request['account'] = $accountCategory;
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $response = $this->v2PrivateDataGetOrderHist ($this->extend($request, $query));
        } elseif ($method === 'v2PrivateAccountGroupGetFuturesOrderHistCurrent') {
            $request['account-group'] = $accountGroup;
            $request['account-category'] = $accountCategory;
            if ($limit !== null) {
                $request['pageSize'] = $limit;
            }
            $response = $this->v2PrivateAccountGroupGetFuturesOrderHistCurrent ($this->extend($request, $query));
        } else {
            throw new NotSupported($this->id . ' fetchClosedOrders() is not currently supported for ' . $type . ' markets');
        }
        //
        // accountCategoryGetOrderHistCurrent
        //
        //     {
        //         "code":0,
        //         "accountId":"cshrHKLZCjlZ2ejqkmvIHHtPmLYqdnda",
        //         "ac":"CASH",
        //         "data":array(
        //             {
        //                 "seqNum":15561826728,
        //                 "orderId":"a17294d305c0U6491137460bethu7kw9",
        //                 "symbol":"ETH/USDT",
        //                 "orderType":"Limit",
        //                 "lastExecTime":1591635618200,
        //                 "price":"200",
        //                 "orderQty":"0.1",
        //                 "side":"Buy",
        //                 "status":"Canceled",
        //                 "avgPx":"0",
        //                 "cumFilledQty":"0",
        //                 "stopPrice":"",
        //                 "errorCode":"",
        //                 "cumFee":"0",
        //                 "feeAsset":"USDT",
        //                 "execInst":"NULL_VAL"
        //             }
        //         )
        //     }
        //
        //    {
        //        "code" => 0,
        //        "data" => array(
        //            {
        //                "orderId"     :  "a173ad938fc3U22666567717788c3b66", // orderId
        //                "seqNum"      :  18777366360,                        // sequence number
        //                "accountId"   :  "cshwSjbpPjSwHmxPdz2CPQVU9mnbzPpt", // accountId
        //                "symbol"      :  "BTC/USDT",                         // $symbol
        //                "orderType"   :  "Limit",                            // order $type (Limit/Market/StopMarket/StopLimit)
        //                "side"        :  "Sell",                             // order side (Buy/Sell)
        //                "price"       :  "11346.77",                         // order price
        //                "stopPrice"   :  "0",                                // stop price (0 by default)
        //                "orderQty"    :  "0.01",                             // order quantity (in base asset)
        //                "status"      :  "Canceled",                         // order status (Filled/Canceled/Rejected)
        //                "createTime"  :  1596344995793,                      // order creation time
        //                "lastExecTime" =>  1596344996053,                      // last execution time
        //                "avgFillPrice" =>  "11346.77",                         // average filled price
        //                "fillQty"     :  "0.01",                             // filled quantity (in base asset)
        //                "fee"         :  "-0.004992579",                     // cummulative fee. if negative, this value is the commission charged; if possitive, this value is the rebate received.
        //                "feeAsset"    :  "USDT"                              // fee asset
        //            }
        //        )
        //    }
        //
        // accountGroupGetFuturesOrderHistCurrent
        //
        //     {
        //         "code" => 0,
        //         "data" => array(
        //             {
        //                 "ac" => "FUTURES",
        //                 "accountId" => "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
        //                 "time" => 1640245777002,
        //                 "orderId" => "r17de6444fa6U0711043490bbtcpJ2lI",
        //                 "seqNum" => 28796124902,
        //                 "orderType" => "Limit",
        //                 "execInst" => "NULL_VAL",
        //                 "side" => "Buy",
        //                 "symbol" => "BTC-PERP",
        //                 "price" => "30000",
        //                 "orderQty" => "0.0021",
        //                 "stopPrice" => "0",
        //                 "stopBy" => "market",
        //                 "status" => "Canceled",
        //                 "lastExecTime" => 1640246574886,
        //                 "lastQty" => "0",
        //                 "lastPx" => "0",
        //                 "avgFilledPx" => "0",
        //                 "cumFilledQty" => "0",
        //                 "fee" => "0",
        //                 "cumFee" => "0",
        //                 "feeAsset" => "USDT",
        //                 "errorCode" => "",
        //                 "posStopLossPrice" => "0",
        //                 "posStopLossTrigger" => "market",
        //                 "posTakeProfitPrice" => "0",
        //                 "posTakeProfitTrigger" => "market",
        //                 "liquidityInd" => "n"
        //             }
        //         )
        //     }
        //
        $data = $this->safe_list($response, 'data', array());
        if (gettype($data) !== 'array' || array_keys($data) !== array_keys(array_keys($data))) {
            $data = $this->safe_list($data, 'data', array());
        }
        return $this->parse_orders($data, $market, $since, $limit);
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * cancels an open $order
         *
         * @see https://ascendex.github.io/ascendex-pro-api/#cancel-$order
         * @see https://ascendex.github.io/ascendex-futures-pro-api-v2/#cancel-$order
         *
         * @param {string} $id $order $id
         * @param {string} $symbol unified $symbol of the $market the $order was made in
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} An ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structure~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' cancelOrder() requires a $symbol argument');
        }
        $this->load_markets();
        $this->load_accounts();
        $market = $this->market($symbol);
        list($type, $query) = $this->handle_market_type_and_params('cancelOrder', $market, $params);
        $accountsByType = $this->safe_dict($this->options, 'accountsByType', array());
        $accountCategory = $this->safe_string($accountsByType, $type, 'cash');
        $account = $this->safe_dict($this->accounts, 0, array());
        $accountGroup = $this->safe_string($account, 'id');
        $request = array(
            'account-group' => $accountGroup,
            'account-category' => $accountCategory,
            'symbol' => $market['id'],
            'time' => $this->milliseconds(),
            'id' => 'foobar',
        );
        $clientOrderId = $this->safe_string_2($params, 'clientOrderId', 'id');
        if ($clientOrderId === null) {
            $request['orderId'] = $id;
        } else {
            $request['id'] = $clientOrderId;
            $params = $this->omit($params, array( 'clientOrderId', 'id' ));
        }
        $response = null;
        if (($type === 'spot') || ($type === 'margin')) {
            $response = $this->v1PrivateAccountCategoryDeleteOrder ($this->extend($request, $query));
        } elseif ($type === 'swap') {
            $request['account-category'] = $accountCategory;
            $response = $this->v2PrivateAccountGroupDeleteFuturesOrder ($this->extend($request, $query));
        } else {
            throw new NotSupported($this->id . ' cancelOrder() is not currently supported for ' . $type . ' markets');
        }
        //
        // AccountCategoryDeleteOrder
        //
        //     {
        //         "code" => 0,
        //         "data" => {
        //             "accountId" => "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo",
        //             "ac" => "CASH",
        //             "action" => "cancel-$order",
        //             "status" => "Ack",
        //             "info" => {
        //                 "id" =>        "wv8QGquoeamhssvQBeHOHGQCGlcBjj23",
        //                 "orderId" =>   "16e6198afb4s8bXHbAwwoqDo2ebc19dc",
        //                 "orderType" => "", // could be empty
        //                 "symbol" =>    "ETH/USDT",
        //                 "timestamp" =>  1573594877822
        //             }
        //         }
        //     }
        //
        // AccountGroupDeleteFuturesOrder
        //
        //     {
        //         "code" => 0,
        //         "data" => {
        //             "meta" => array(
        //                 "id" => "foobar",
        //                 "action" => "cancel-$order",
        //                 "respInst" => "ACK"
        //             ),
        //             "order" => {
        //                 "ac" => "FUTURES",
        //                 "accountId" => "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
        //                 "time" => 1640244480476,
        //                 "orderId" => "r17de63086f4U0711043490bbtcpPUF4",
        //                 "seqNum" => 28795959269,
        //                 "orderType" => "Limit",
        //                 "execInst" => "NULL_VAL",
        //                 "side" => "Buy",
        //                 "symbol" => "BTC-PERP",
        //                 "price" => "30000",
        //                 "orderQty" => "0.0021",
        //                 "stopPrice" => "0",
        //                 "stopBy" => "market",
        //                 "status" => "New",
        //                 "lastExecTime" => 1640244480491,
        //                 "lastQty" => "0",
        //                 "lastPx" => "0",
        //                 "avgFilledPx" => "0",
        //                 "cumFilledQty" => "0",
        //                 "fee" => "0",
        //                 "cumFee" => "0",
        //                 "feeAsset" => "BTCPC",
        //                 "errorCode" => "",
        //                 "posStopLossPrice" => "0",
        //                 "posStopLossTrigger" => "market",
        //                 "posTakeProfitPrice" => "0",
        //                 "posTakeProfitTrigger" => "market",
        //                 "liquidityInd" => "n"
        //             }
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        $order = $this->safe_dict_2($data, 'order', 'info', array());
        return $this->parse_order($order, $market);
    }

    public function cancel_all_orders(?string $symbol = null, $params = array ()) {
        /**
         * cancel all open orders
         *
         * @see https://ascendex.github.io/ascendex-pro-api/#cancel-all-orders
         * @see https://ascendex.github.io/ascendex-futures-pro-api-v2/#cancel-all-open-orders
         *
         * @param {string} $symbol unified $market $symbol, only orders in the $market of this $symbol are cancelled when $symbol is not null
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list with a single ~@link https://docs.ccxt.com/#/?id=order-structure order structure~ with the $response assigned to the info property
         */
        $this->load_markets();
        $this->load_accounts();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        list($type, $query) = $this->handle_market_type_and_params('cancelAllOrders', $market, $params);
        $accountsByType = $this->safe_dict($this->options, 'accountsByType', array());
        $accountCategory = $this->safe_string($accountsByType, $type, 'cash');
        $account = $this->safe_dict($this->accounts, 0, array());
        $accountGroup = $this->safe_string($account, 'id');
        $request = array(
            'account-group' => $accountGroup,
            'account-category' => $accountCategory,
            'time' => $this->milliseconds(),
        );
        if ($symbol !== null) {
            $request['symbol'] = $market['id'];
        }
        $response = null;
        if (($type === 'spot') || ($type === 'margin')) {
            $response = $this->v1PrivateAccountCategoryDeleteOrderAll ($this->extend($request, $query));
        } elseif ($type === 'swap') {
            $request['account-category'] = $accountCategory;
            $response = $this->v2PrivateAccountGroupDeleteFuturesOrderAll ($this->extend($request, $query));
        } else {
            throw new NotSupported($this->id . ' cancelAllOrders() is not currently supported for ' . $type . ' markets');
        }
        //
        // AccountCategoryDeleteOrderAll
        //
        //     {
        //         "code" => 0,
        //         "data" => {
        //             "ac" => "CASH",
        //             "accountId" => "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo",
        //             "action" => "cancel-all",
        //             "info" => array(
        //                 "id" =>  "2bmYvi7lyTrneMzpcJcf2D7Pe9V1P9wy",
        //                 "orderId" => "",
        //                 "orderType" => "NULL_VAL",
        //                 "symbol" => "",
        //                 "timestamp" => 1574118495462
        //             ),
        //             "status" => "Ack"
        //         }
        //     }
        //
        // AccountGroupDeleteFuturesOrderAll
        //
        //     {
        //         "code" => 0,
        //         "data" => {
        //             "ac" => "FUTURES",
        //             "accountId" => "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
        //             "action" => "cancel-all",
        //             "info" => {
        //                 "symbol":"BTC-PERP"
        //             }
        //         }
        //     }
        //
        return array( $this->safe_order(array(
            'info' => $response,
        )) );
    }

    public function parse_deposit_address($depositAddress, ?array $currency = null): array {
        //
        //     {
        //         "address" => "0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722",
        //         "destTag" => "",
        //         "tagType" => "",
        //         "tagId" => "",
        //         "chainName" => "ERC20",
        //         "numConfirmations" => 20,
        //         "withdrawalFee" => 1,
        //         "nativeScale" => 4,
        //         "tips" => array()
        //     }
        //
        $address = $this->safe_string($depositAddress, 'address');
        $tagId = $this->safe_string($depositAddress, 'tagId');
        $tag = $this->safe_string($depositAddress, $tagId);
        $this->check_address($address);
        $code = ($currency === null) ? null : $currency['code'];
        $chainName = $this->safe_string($depositAddress, 'blockchain');
        $network = $this->network_id_to_code($chainName, $code);
        return array(
            'info' => $depositAddress,
            'currency' => $code,
            'network' => $network,
            'address' => $address,
            'tag' => $tag,
        );
    }

    public function fetch_deposit_address(string $code, $params = array ()): array {
        /**
         * fetch the deposit $address for a $currency associated with this account
         *
         * @see https://ascendex.github.io/ascendex-pro-api/#query-deposit-$addresses
         *
         * @param {string} $code unified $currency $code
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->network] unified network $code for deposit chain
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=$address-structure $address structure~
         */
        $this->load_markets();
        $currency = $this->currency($code);
        $networkCode = $this->safe_string_2($params, 'network', 'chainName');
        $networkId = $this->network_code_to_id($networkCode);
        $params = $this->omit($params, array( 'chainName' ));
        $request = array(
            'asset' => $currency['id'],
            'blockchain' => $networkId,
        );
        $response = $this->v1PrivateGetWalletDepositAddress ($this->extend($request, $params));
        //
        //     {
        //         "code":0,
        //         "data":{
        //             "asset":"USDT",
        //             "assetName":"Tether",
        //             "address":array(
        //                 array(
        //                     "address":"1N22odLHXnLPCjC8kwBJPTayarr9RtPod6",
        //                     "destTag":"",
        //                     "tagType":"",
        //                     "tagId":"",
        //                     "chainName":"Omni",
        //                     "numConfirmations":3,
        //                     "withdrawalFee":4.7,
        //                     "nativeScale":4,
        //                     "tips":array()
        //                 ),
        //                 {
        //                     "address":"0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722",
        //                     "destTag":"",
        //                     "tagType":"",
        //                     "tagId":"",
        //                     "chainName":"ERC20",
        //                     "numConfirmations":20,
        //                     "withdrawalFee":1.0,
        //                     "nativeScale":4,
        //                     "tips":array()
        //                 }
        //             )
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        $addresses = $this->safe_list($data, 'address', array());
        $numAddresses = count($addresses);
        $address = null;
        if ($numAddresses > 1) {
            $addressesByChainName = $this->index_by($addresses, 'chainName');
            if ($networkId === null) {
                $chainNames = is_array($addressesByChainName) ? array_keys($addressesByChainName) : array();
                $chains = implode(', ', $chainNames);
                throw new ArgumentsRequired($this->id . ' fetchDepositAddress() returned more than one $address, a chainName parameter is required, one of ' . $chains);
            }
            $address = $this->safe_dict($addressesByChainName, $networkId, array());
        } else {
            // first $address
            $address = $this->safe_dict($addresses, 0, array());
        }
        $result = $this->parse_deposit_address($address, $currency);
        return $this->extend($result, array(
            'info' => $response,
        ));
    }

    public function fetch_deposits(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all deposits made to an account
         * @param {string} $code unified currency $code
         * @param {int} [$since] the earliest time in ms to fetch deposits for
         * @param {int} [$limit] the maximum number of deposits structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $request = array(
            'txType' => 'deposit',
        );
        return $this->fetch_transactions($code, $since, $limit, $this->extend($request, $params));
    }

    public function fetch_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all withdrawals made from an account
         * @param {string} $code unified currency $code
         * @param {int} [$since] the earliest time in ms to fetch withdrawals for
         * @param {int} [$limit] the maximum number of withdrawals structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $request = array(
            'txType' => 'withdrawal',
        );
        return $this->fetch_transactions($code, $since, $limit, $this->extend($request, $params));
    }

    public function fetch_deposits_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch history of deposits and withdrawals
         * @param {string} [$code] unified $currency $code for the $currency of the deposit/withdrawals, default is null
         * @param {int} [$since] timestamp in ms of the earliest deposit/withdrawal, default is null
         * @param {int} [$limit] max number of deposit/withdrawals to return, default is null
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structure~
         */
        $this->load_markets();
        $request = array(
            // 'asset' => $currency['id'],
            // 'page' => 1,
            // 'pageSize' => 20,
            // 'startTs' => $this->milliseconds(),
            // 'endTs' => $this->milliseconds(),
            // 'txType' => undefned, // deposit, withdrawal
        );
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['asset'] = $currency['id'];
        }
        if ($since !== null) {
            $request['startTs'] = $since;
        }
        if ($limit !== null) {
            $request['pageSize'] = $limit;
        }
        $response = $this->v1PrivateGetWalletTransactions ($this->extend($request, $params));
        //
        //     {
        //         "code" => 0,
        //         "data" => {
        //             "data" => array(
        //                 {
        //                     "requestId" => "wuzd1Ojsqtz4bCA3UXwtUnnJDmU8PiyB",
        //                     "time" => 1591606166000,
        //                     "asset" => "USDT",
        //                     "transactionType" => "deposit",
        //                     "amount" => "25",
        //                     "commission" => "0",
        //                     "networkTransactionId" => "0xbc4eabdce92f14dbcc01d799a5f8ca1f02f4a3a804b6350ea202be4d3c738fce",
        //                     "status" => "pending",
        //                     "numConfirmed" => 8,
        //                     "numConfirmations" => 20,
        //                     "destAddress" => array( address => "0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722" )
        //                 }
        //             ),
        //             "page" => 1,
        //             "pageSize" => 20,
        //             "hasNext" => false
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        $transactions = $this->safe_list($data, 'data', array());
        return $this->parse_transactions($transactions, $currency, $since, $limit);
    }

    public function parse_transaction_status(?string $status) {
        $statuses = array(
            'reviewing' => 'pending',
            'pending' => 'pending',
            'confirmed' => 'ok',
            'rejected' => 'rejected',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_transaction(array $transaction, ?array $currency = null): array {
        //
        //     {
        //         "requestId" => "wuzd1Ojsqtz4bCA3UXwtUnnJDmU8PiyB",
        //         "time" => 1591606166000,
        //         "asset" => "USDT",
        //         "transactionType" => "deposit",
        //         "amount" => "25",
        //         "commission" => "0",
        //         "networkTransactionId" => "0xbc4eabdce92f14dbcc01d799a5f8ca1f02f4a3a804b6350ea202be4d3c738fce",
        //         "status" => "pending",
        //         "numConfirmed" => 8,
        //         "numConfirmations" => 20,
        //         "destAddress" => {
        //             "address" => "0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722",
        //             "destTag" => "..." // for currencies that have it
        //         }
        //     }
        //
        $destAddress = $this->safe_dict($transaction, 'destAddress', array());
        $address = $this->safe_string($destAddress, 'address');
        $tag = $this->safe_string($destAddress, 'destTag');
        $timestamp = $this->safe_integer($transaction, 'time');
        $currencyId = $this->safe_string($transaction, 'asset');
        $amountString = $this->safe_string($transaction, 'amount');
        $feeCostString = $this->safe_string($transaction, 'commission');
        $amountString = Precise::string_sub($amountString, $feeCostString);
        $code = $this->safe_currency_code($currencyId, $currency);
        return array(
            'info' => $transaction,
            'id' => $this->safe_string($transaction, 'requestId'),
            'txid' => $this->safe_string($transaction, 'networkTransactionId'),
            'type' => $this->safe_string($transaction, 'transactionType'),
            'currency' => $code,
            'network' => null,
            'amount' => $this->parse_number($amountString),
            'status' => $this->parse_transaction_status($this->safe_string($transaction, 'status')),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'address' => $address,
            'addressFrom' => null,
            'addressTo' => $address,
            'tag' => $tag,
            'tagFrom' => null,
            'tagTo' => $tag,
            'updated' => null,
            'comment' => null,
            'fee' => array(
                'currency' => $code,
                'cost' => $this->parse_number($feeCostString),
                'rate' => null,
            ),
            'internal' => false,
        );
    }

    public function fetch_positions(?array $symbols = null, $params = array ()): array {
        /**
         * fetch all open positions
         * @param {string[]|null} $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~
         */
        $this->load_markets();
        $this->load_accounts();
        $account = $this->safe_dict($this->accounts, 0, array());
        $accountGroup = $this->safe_string($account, 'id');
        $request = array(
            'account-group' => $accountGroup,
        );
        $response = $this->v2PrivateAccountGroupGetFuturesPosition ($this->extend($request, $params));
        //
        //     {
        //         "code" => 0,
        //         "data" => {
        //             "accountId" => "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
        //             "ac" => "FUTURES",
        //             "collaterals" => array(
        //                 {
        //                     "asset" => "USDT",
        //                     "balance" => "44.570287262",
        //                     "referencePrice" => "1",
        //                     "discountFactor" => "1"
        //                 }
        //             ),
        //             "contracts" => array(
        //                 array(
        //                     "symbol" => "BTC-PERP",
        //                     "side" => "LONG",
        //                     "position" => "0.0001",
        //                     "referenceCost" => "-3.12277254",
        //                     "unrealizedPnl" => "-0.001700233",
        //                     "realizedPnl" => "0",
        //                     "avgOpenPrice" => "31209",
        //                     "marginType" => "isolated",
        //                     "isolatedMargin" => "1.654972977",
        //                     "leverage" => "2",
        //                     "takeProfitPrice" => "0",
        //                     "takeProfitTrigger" => "market",
        //                     "stopLossPrice" => "0",
        //                     "stopLossTrigger" => "market",
        //                     "buyOpenOrderNotional" => "0",
        //                     "sellOpenOrderNotional" => "0",
        //                     "markPrice" => "31210.723063672",
        //                     "indexPrice" => "31223.148857925"
        //                 ),
        //             )
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        $position = $this->safe_list($data, 'contracts', array());
        $result = array();
        for ($i = 0; $i < count($position); $i++) {
            $result[] = $this->parse_position($position[$i]);
        }
        $symbols = $this->market_symbols($symbols);
        return $this->filter_by_array_positions($result, 'symbol', $symbols, false);
    }

    public function parse_position(array $position, ?array $market = null) {
        //
        //     array(
        //         "symbol" => "BTC-PERP",
        //         "side" => "LONG",
        //         "position" => "0.0001",
        //         "referenceCost" => "-3.12277254",
        //         "unrealizedPnl" => "-0.001700233",
        //         "realizedPnl" => "0",
        //         "avgOpenPrice" => "31209",
        //         "marginType" => "isolated",
        //         "isolatedMargin" => "1.654972977",
        //         "leverage" => "2",
        //         "takeProfitPrice" => "0",
        //         "takeProfitTrigger" => "market",
        //         "stopLossPrice" => "0",
        //         "stopLossTrigger" => "market",
        //         "buyOpenOrderNotional" => "0",
        //         "sellOpenOrderNotional" => "0",
        //         "markPrice" => "31210.723063672",
        //         "indexPrice" => "31223.148857925"
        //     ),
        //
        $marketId = $this->safe_string($position, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $notional = $this->safe_string($position, 'buyOpenOrderNotional');
        if (Precise::string_eq($notional, '0')) {
            $notional = $this->safe_string($position, 'sellOpenOrderNotional');
        }
        $marginType = $this->safe_string($position, 'marginType');
        $marginMode = ($marginType === 'crossed') ? 'cross' : 'isolated';
        $collateral = null;
        if ($marginMode === 'isolated') {
            $collateral = $this->safe_string($position, 'isolatedMargin');
        }
        return $this->safe_position(array(
            'info' => $position,
            'id' => null,
            'symbol' => $market['symbol'],
            'notional' => $this->parse_number($notional),
            'marginMode' => $marginMode,
            'liquidationPrice' => null,
            'entryPrice' => $this->safe_number($position, 'avgOpenPrice'),
            'unrealizedPnl' => $this->safe_number($position, 'unrealizedPnl'),
            'percentage' => null,
            'contracts' => $this->safe_number($position, 'position'),
            'contractSize' => $this->safe_number($market, 'contractSize'),
            'markPrice' => $this->safe_number($position, 'markPrice'),
            'lastPrice' => null,
            'side' => $this->safe_string_lower($position, 'side'),
            'hedged' => null,
            'timestamp' => null,
            'datetime' => null,
            'lastUpdateTimestamp' => null,
            'maintenanceMargin' => null,
            'maintenanceMarginPercentage' => null,
            'collateral' => $collateral,
            'initialMargin' => null,
            'initialMarginPercentage' => null,
            'leverage' => $this->safe_integer($position, 'leverage'),
            'marginRatio' => null,
            'stopLossPrice' => $this->safe_number($position, 'stopLossPrice'),
            'takeProfitPrice' => $this->safe_number($position, 'takeProfitPrice'),
        ));
    }

    public function parse_funding_rate($contract, ?array $market = null): array {
        //
        //      {
        //          "time" => 1640061364830,
        //          "symbol" => "EOS-PERP",
        //          "markPrice" => "3.353854865",
        //          "indexPrice" => "3.3542",
        //          "openInterest" => "14242",
        //          "fundingRate" => "-0.000073026",
        //          "nextFundingTime" => 1640073600000
        //      }
        //
        $marketId = $this->safe_string($contract, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $currentTime = $this->safe_integer($contract, 'time');
        $nextFundingRate = $this->safe_number($contract, 'fundingRate');
        $nextFundingRateTimestamp = $this->safe_integer($contract, 'nextFundingTime');
        return array(
            'info' => $contract,
            'symbol' => $symbol,
            'markPrice' => $this->safe_number($contract, 'markPrice'),
            'indexPrice' => $this->safe_number($contract, 'indexPrice'),
            'interestRate' => $this->parse_number('0'),
            'estimatedSettlePrice' => null,
            'timestamp' => $currentTime,
            'datetime' => $this->iso8601($currentTime),
            'previousFundingRate' => null,
            'nextFundingRate' => null,
            'previousFundingTimestamp' => null,
            'nextFundingTimestamp' => null,
            'previousFundingDatetime' => null,
            'nextFundingDatetime' => null,
            'fundingRate' => $nextFundingRate,
            'fundingTimestamp' => $nextFundingRateTimestamp,
            'fundingDatetime' => $this->iso8601($nextFundingRateTimestamp),
            'interval' => null,
        );
    }

    public function fetch_funding_rates(?array $symbols = null, $params = array ()): array {
        /**
         * fetch the funding rate for multiple markets
         * @param {string[]|null} $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=funding-rates-structure funding rates structures~, indexe by market $symbols
         */
        $this->load_markets();
        $symbols = $this->market_symbols($symbols);
        $response = $this->v2PublicGetFuturesPricingData ($params);
        //
        //     {
        //          "code" => 0,
        //          "data" => {
        //              "contracts" => array(
        //                  array(
        //                      "time" => 1640061364830,
        //                      "symbol" => "EOS-PERP",
        //                      "markPrice" => "3.353854865",
        //                      "indexPrice" => "3.3542",
        //                      "openInterest" => "14242",
        //                      "fundingRate" => "-0.000073026",
        //                      "nextFundingTime" => 1640073600000
        //                  ),
        //              ),
        //              "collaterals" => array(
        //                  array(
        //                      "asset" => "USDTR",
        //                      "referencePrice" => "1"
        //                  ),
        //              )
        //          }
        //      }
        //
        $data = $this->safe_dict($response, 'data', array());
        $contracts = $this->safe_list($data, 'contracts', array());
        return $this->parse_funding_rates($contracts, $symbols);
    }

    public function modify_margin_helper(string $symbol, $amount, $type, $params = array ()): array {
        $this->load_markets();
        $this->load_accounts();
        $market = $this->market($symbol);
        $account = $this->safe_dict($this->accounts, 0, array());
        $accountGroup = $this->safe_string($account, 'id');
        $amount = $this->amount_to_precision($symbol, $amount);
        $request = array(
            'account-group' => $accountGroup,
            'symbol' => $market['id'],
            'amount' => $amount, // positive value for adding margin, negative for reducing
        );
        $response = $this->v2PrivateAccountGroupPostFuturesIsolatedPositionMargin ($this->extend($request, $params));
        //
        // Can only change margin for perpetual futures isolated margin positions
        //
        //     {
        //          "code" => 0
        //     }
        //
        if ($type === 'reduce') {
            $amount = Precise::string_abs($amount);
        }
        return $this->extend($this->parse_margin_modification($response, $market), array(
            'amount' => $this->parse_number($amount),
            'type' => $type,
        ));
    }

    public function parse_margin_modification(array $data, ?array $market = null): array {
        //
        // addMargin/reduceMargin
        //
        //     {
        //          "code" => 0
        //     }
        //
        $errorCode = $this->safe_string($data, 'code');
        $status = ($errorCode === '0') ? 'ok' : 'failed';
        return array(
            'info' => $data,
            'symbol' => $market['symbol'],
            'type' => null,
            'marginMode' => 'isolated',
            'amount' => null,
            'total' => null,
            'code' => $market['quote'],
            'status' => $status,
            'timestamp' => null,
            'datetime' => null,
        );
    }

    public function reduce_margin(string $symbol, float $amount, $params = array ()): array {
        /**
         * remove margin from a position
         * @param {string} $symbol unified market $symbol
         * @param {float} $amount the $amount of margin to remove
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=reduce-margin-structure margin structure~
         */
        return $this->modify_margin_helper($symbol, -$amount, 'reduce', $params);
    }

    public function add_margin(string $symbol, float $amount, $params = array ()): array {
        /**
         * add margin
         * @param {string} $symbol unified market $symbol
         * @param {float} $amount amount of margin to add
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=add-margin-structure margin structure~
         */
        return $this->modify_margin_helper($symbol, $amount, 'add', $params);
    }

    public function set_leverage(int $leverage, ?string $symbol = null, $params = array ()) {
        /**
         * set the level of $leverage for a $market
         *
         * @see https://ascendex.github.io/ascendex-futures-pro-api-v2/#change-contract-$leverage
         *
         * @param {float} $leverage the rate of $leverage
         * @param {string} $symbol unified $market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} response from the exchange
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' setLeverage() requires a $symbol argument');
        }
        if (($leverage < 1) || ($leverage > 100)) {
            throw new BadRequest($this->id . ' $leverage should be between 1 and 100');
        }
        $this->load_markets();
        $this->load_accounts();
        $market = $this->market($symbol);
        if (!$market['swap']) {
            throw new BadSymbol($this->id . ' setLeverage() supports swap contracts only');
        }
        $account = $this->safe_dict($this->accounts, 0, array());
        $accountGroup = $this->safe_string($account, 'id');
        $request = array(
            'account-group' => $accountGroup,
            'symbol' => $market['id'],
            'leverage' => $leverage,
        );
        return $this->v2PrivateAccountGroupPostFuturesLeverage ($this->extend($request, $params));
    }

    public function set_margin_mode(string $marginMode, ?string $symbol = null, $params = array ()) {
        /**
         * set margin mode to 'cross' or 'isolated'
         *
         * @see https://ascendex.github.io/ascendex-futures-pro-api-v2/#change-margin-type
         *
         * @param {string} $marginMode 'cross' or 'isolated'
         * @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 . ' setMarginMode() requires a $symbol argument');
        }
        $marginMode = strtolower($marginMode);
        if ($marginMode === 'cross') {
            $marginMode = 'crossed';
        }
        if ($marginMode !== 'isolated' && $marginMode !== 'crossed') {
            throw new BadRequest($this->id . ' setMarginMode() $marginMode argument should be isolated or cross');
        }
        $this->load_markets();
        $this->load_accounts();
        $market = $this->market($symbol);
        $account = $this->safe_dict($this->accounts, 0, array());
        $accountGroup = $this->safe_string($account, 'id');
        $request = array(
            'account-group' => $accountGroup,
            'symbol' => $market['id'],
            'marginType' => $marginMode,
        );
        if (!$market['swap']) {
            throw new BadSymbol($this->id . ' setMarginMode() supports swap contracts only');
        }
        return $this->v2PrivateAccountGroupPostFuturesMarginType ($this->extend($request, $params));
    }

    public function fetch_leverage_tiers(?array $symbols = null, $params = array ()): array {
        /**
         * retrieve information on the maximum leverage, and maintenance margin for trades of varying trade sizes
         * @param {string[]|null} $symbols list of unified market $symbols
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=leverage-tiers-structure leverage tiers structures~, indexed by market $symbols
         */
        $this->load_markets();
        $response = $this->v2PublicGetFuturesContract ($params);
        //
        //     {
        //         "code":0,
        //         "data":array(
        //             {
        //                 "symbol":"BTC-PERP",
        //                 "status":"Normal",
        //                 "displayName":"BTCUSDT",
        //                 "settlementAsset":"USDT",
        //                 "underlying":"BTC/USDT",
        //                 "tradingStartTime":1579701600000,
        //                 "priceFilter":array("minPrice":"1","maxPrice":"1000000","tickSize":"1"),
        //                 "lotSizeFilter":array("minQty":"0.0001","maxQty":"1000000000","lotSize":"0.0001"),
        //                 "commissionType":"Quote",
        //                 "commissionReserveRate":"0.001",
        //                 "marketOrderPriceMarkup":"0.03",
        //                 "marginRequirements":array(
        //                     array("positionNotionalLowerBound":"0","positionNotionalUpperBound":"50000","initialMarginRate":"0.01","maintenanceMarginRate":"0.006"),
        //                     array("positionNotionalLowerBound":"50000","positionNotionalUpperBound":"200000","initialMarginRate":"0.02","maintenanceMarginRate":"0.012"),
        //                     array("positionNotionalLowerBound":"200000","positionNotionalUpperBound":"2000000","initialMarginRate":"0.04","maintenanceMarginRate":"0.024"),
        //                     array("positionNotionalLowerBound":"2000000","positionNotionalUpperBound":"20000000","initialMarginRate":"0.1","maintenanceMarginRate":"0.06"),
        //                     array("positionNotionalLowerBound":"20000000","positionNotionalUpperBound":"40000000","initialMarginRate":"0.2","maintenanceMarginRate":"0.12"),
        //                     array("positionNotionalLowerBound":"40000000","positionNotionalUpperBound":"1000000000","initialMarginRate":"0.333333","maintenanceMarginRate":"0.2")
        //                 )
        //             }
        //         )
        //     }
        //
        $data = $this->safe_list($response, 'data', array());
        $symbols = $this->market_symbols($symbols);
        return $this->parse_leverage_tiers($data, $symbols, 'symbol');
    }

    public function parse_market_leverage_tiers($info, ?array $market = null): array {
        /**
         * @param {array} $info Exchange $market response for 1 $market
         * @param {array} $market CCXT $market
         */
        //
        //    {
        //        "symbol":"BTC-PERP",
        //        "status":"Normal",
        //        "displayName":"BTCUSDT",
        //        "settlementAsset":"USDT",
        //        "underlying":"BTC/USDT",
        //        "tradingStartTime":1579701600000,
        //        "priceFilter":array("minPrice":"1","maxPrice":"1000000","tickSize":"1"),
        //        "lotSizeFilter":array("minQty":"0.0001","maxQty":"1000000000","lotSize":"0.0001"),
        //        "commissionType":"Quote",
        //        "commissionReserveRate":"0.001",
        //        "marketOrderPriceMarkup":"0.03",
        //        "marginRequirements":array(
        //            array("positionNotionalLowerBound":"0","positionNotionalUpperBound":"50000","initialMarginRate":"0.01","maintenanceMarginRate":"0.006"),
        //            array("positionNotionalLowerBound":"50000","positionNotionalUpperBound":"200000","initialMarginRate":"0.02","maintenanceMarginRate":"0.012"),
        //            array("positionNotionalLowerBound":"200000","positionNotionalUpperBound":"2000000","initialMarginRate":"0.04","maintenanceMarginRate":"0.024"),
        //            array("positionNotionalLowerBound":"2000000","positionNotionalUpperBound":"20000000","initialMarginRate":"0.1","maintenanceMarginRate":"0.06"),
        //            array("positionNotionalLowerBound":"20000000","positionNotionalUpperBound":"40000000","initialMarginRate":"0.2","maintenanceMarginRate":"0.12"),
        //            array("positionNotionalLowerBound":"40000000","positionNotionalUpperBound":"1000000000","initialMarginRate":"0.333333","maintenanceMarginRate":"0.2")
        //        )
        //    }
        //
        $marginRequirements = $this->safe_list($info, 'marginRequirements', array());
        $marketId = $this->safe_string($info, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $tiers = array();
        for ($i = 0; $i < count($marginRequirements); $i++) {
            $tier = $marginRequirements[$i];
            $initialMarginRate = $this->safe_string($tier, 'initialMarginRate');
            $tiers[] = array(
                'tier' => $this->sum($i, 1),
                'symbol' => $this->safe_symbol($marketId, $market, null, 'contract'),
                'currency' => $market['quote'],
                'minNotional' => $this->safe_number($tier, 'positionNotionalLowerBound'),
                'maxNotional' => $this->safe_number($tier, 'positionNotionalUpperBound'),
                'maintenanceMarginRate' => $this->safe_number($tier, 'maintenanceMarginRate'),
                'maxLeverage' => $this->parse_number(Precise::string_div('1', $initialMarginRate)),
                'info' => $tier,
            );
        }
        return $tiers;
    }

    public function parse_deposit_withdraw_fee($fee, ?array $currency = null) {
        //
        // {
        //     "assetCode" =>      "USDT",
        //     "assetName" =>      "Tether",
        //     "precisionScale" =>  9,
        //     "nativeScale" =>     4,
        //     "blockChain" => array(
        //         array(
        //             "chainName" =>        "Omni",
        //             "withdrawFee" =>      "30.0",
        //             "allowDeposit" =>      true,
        //             "allowWithdraw" =>     true,
        //             "minDepositAmt" =>    "0.0",
        //             "minWithdrawal" =>    "50.0",
        //             "numConfirmations" =>  3
        //         ),
        //     )
        // }
        //
        $blockChains = $this->safe_list($fee, 'blockChain', array());
        $blockChainsLength = count($blockChains);
        $result = array(
            'info' => $fee,
            'withdraw' => array(
                'fee' => null,
                'percentage' => null,
            ),
            'deposit' => array(
                'fee' => null,
                'percentage' => null,
            ),
            'networks' => array(),
        );
        for ($i = 0; $i < $blockChainsLength; $i++) {
            $blockChain = $blockChains[$i];
            $networkId = $this->safe_string($blockChain, 'chainName');
            $currencyCode = $this->safe_string($currency, 'code');
            $networkCode = $this->network_id_to_code($networkId, $currencyCode);
            $result['networks'][$networkCode] = array(
                'deposit' => array( 'fee' => null, 'percentage' => null ),
                'withdraw' => array( 'fee' => $this->safe_number($blockChain, 'withdrawFee'), 'percentage' => false ),
            );
            if ($blockChainsLength === 1) {
                $result['withdraw']['fee'] = $this->safe_number($blockChain, 'withdrawFee');
                $result['withdraw']['percentage'] = false;
            }
        }
        return $result;
    }

    public function fetch_deposit_withdraw_fees(?array $codes = null, $params = array ()) {
        /**
         * fetch deposit and withdraw fees
         *
         * @see https://ascendex.github.io/ascendex-pro-api/#list-all-assets
         *
         * @param {string[]|null} $codes list of unified currency $codes
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a list of ~@link https://docs.ccxt.com/#/?id=fee-structure fee structures~
         */
        $this->load_markets();
        $response = $this->v2PublicGetAssets ($params);
        $data = $this->safe_list($response, 'data');
        return $this->parse_deposit_withdraw_fees($data, $codes, 'assetCode');
    }

    public function transfer(string $code, float $amount, string $fromAccount, string $toAccount, $params = array ()): array {
        /**
         * $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
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=$transfer-structure $transfer structure~
         */
        $this->load_markets();
        $this->load_accounts();
        $account = $this->safe_dict($this->accounts, 0, array());
        $accountGroup = $this->safe_string($account, 'id');
        $currency = $this->currency($code);
        $accountsByType = $this->safe_dict($this->options, 'accountsByType', array());
        $fromId = $this->safe_string($accountsByType, $fromAccount, $fromAccount);
        $toId = $this->safe_string($accountsByType, $toAccount, $toAccount);
        if ($fromId !== 'cash' && $toId !== 'cash') {
            throw new ExchangeError($this->id . ' $transfer() only supports direct balance $transfer between spot and swap, spot and margin');
        }
        $request = array(
            'account-group' => $accountGroup,
            'amount' => $this->currency_to_precision($code, $amount),
            'asset' => $currency['id'],
            'fromAccount' => $fromId,
            'toAccount' => $toId,
        );
        $response = $this->v1PrivateAccountGroupPostTransfer ($this->extend($request, $params));
        //
        //    array( "code" => "0" )
        //
        $transferOptions = $this->safe_dict($this->options, 'transfer', array());
        $fillResponseFromRequest = $this->safe_bool($transferOptions, 'fillResponseFromRequest', true);
        $transfer = $this->parse_transfer($response, $currency);
        if ($fillResponseFromRequest) {
            $transfer['fromAccount'] = $fromAccount;
            $transfer['toAccount'] = $toAccount;
            $transfer['amount'] = $amount;
            $transfer['currency'] = $code;
        }
        return $transfer;
    }

    public function parse_transfer(array $transfer, ?array $currency = null): array {
        //
        //    array( "code" => "0" )
        //
        $status = $this->safe_string($transfer, 'code');
        $currencyCode = $this->safe_currency_code(null, $currency);
        return array(
            'info' => $transfer,
            'id' => null,
            'timestamp' => null,
            'datetime' => null,
            'currency' => $currencyCode,
            'amount' => null,
            'fromAccount' => null,
            'toAccount' => null,
            'status' => $this->parse_transfer_status($status),
        );
    }

    public function parse_transfer_status(?string $status): ?string {
        if ($status === '0') {
            return 'ok';
        }
        return 'failed';
    }

    public function fetch_funding_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch the history of funding payments paid and received on this $account
         *
         * @see https://ascendex.github.io/ascendex-futures-pro-api-v2/#funding-payment-history
         *
         * @param {string} [$symbol] unified $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch funding history for
         * @param {int} [$limit] the maximum number of funding history structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=funding-history-structure funding history structure~
         */
        $this->load_markets();
        $this->load_accounts();
        $paginate = false;
        list($paginate, $params) = $this->handle_option_and_params($params, 'fetchFundingHistory', 'paginate');
        if ($paginate) {
            return $this->fetch_paginated_call_incremental('fetchFundingHistory', $symbol, $since, $limit, $params, 'page', 25);
        }
        $account = $this->safe_dict($this->accounts, 0, array());
        $accountGroup = $this->safe_string($account, 'id');
        $request = array(
            'account-group' => $accountGroup,
        );
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        if ($limit !== null) {
            $request['pageSize'] = $limit;
        }
        $response = $this->v2PrivateAccountGroupGetFuturesFundingPayments ($this->extend($request, $params));
        //
        //     {
        //         "code" => 0,
        //         "data" => {
        //             "data" => array(
        //                 array(
        //                     "timestamp" => 1640476800000,
        //                     "symbol" => "BTC-PERP",
        //                     "paymentInUSDT" => "-0.013991178",
        //                     "fundingRate" => "0.000173497"
        //                 ),
        //             ),
        //             "page" => 1,
        //             "pageSize" => 3,
        //             "hasNext" => true
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        $rows = $this->safe_list($data, 'data', array());
        return $this->parse_incomes($rows, $market, $since, $limit);
    }

    public function parse_income($income, ?array $market = null) {
        //
        //     {
        //         "timestamp" => 1640476800000,
        //         "symbol" => "BTC-PERP",
        //         "paymentInUSDT" => "-0.013991178",
        //         "fundingRate" => "0.000173497"
        //     }
        //
        $marketId = $this->safe_string($income, 'symbol');
        $timestamp = $this->safe_integer($income, 'timestamp');
        return array(
            'info' => $income,
            'symbol' => $this->safe_symbol($marketId, $market, '-', 'swap'),
            'code' => 'USDT',
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'id' => null,
            'amount' => $this->safe_number($income, 'paymentInUSDT'),
        );
    }

    public function fetch_margin_modes(?array $symbols = null, $params = array ()): array {
        /**
         * fetches the set margin mode of the user
         *
         * @see https://ascendex.github.io/ascendex-futures-pro-api-v2/#position
         *
         * @param {string[]} [$symbols] a 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=margin-mode-structure margin mode structures~
         */
        $this->load_markets();
        $this->load_accounts();
        $account = $this->safe_dict($this->accounts, 0, array());
        $accountGroup = $this->safe_string($account, 'id');
        $request = array(
            'account-group' => $accountGroup,
        );
        $response = $this->v2PrivateAccountGroupGetFuturesPosition ($this->extend($request, $params));
        //
        //     {
        //         "code" => 0,
        //         "data" => {
        //             "accountId" => "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
        //             "ac" => "FUTURES",
        //             "collaterals" => array(
        //                 {
        //                     "asset" => "USDT",
        //                     "balance" => "44.570287262",
        //                     "referencePrice" => "1",
        //                     "discountFactor" => "1"
        //                 }
        //             ),
        //             "contracts" => array(
        //                 array(
        //                     "symbol" => "BTC-PERP",
        //                     "side" => "LONG",
        //                     "position" => "0.0001",
        //                     "referenceCost" => "-3.12277254",
        //                     "unrealizedPnl" => "-0.001700233",
        //                     "realizedPnl" => "0",
        //                     "avgOpenPrice" => "31209",
        //                     "marginType" => "isolated",
        //                     "isolatedMargin" => "1.654972977",
        //                     "leverage" => "2",
        //                     "takeProfitPrice" => "0",
        //                     "takeProfitTrigger" => "market",
        //                     "stopLossPrice" => "0",
        //                     "stopLossTrigger" => "market",
        //                     "buyOpenOrderNotional" => "0",
        //                     "sellOpenOrderNotional" => "0",
        //                     "markPrice" => "31210.723063672",
        //                     "indexPrice" => "31223.148857925"
        //                 ),
        //             )
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        $marginModes = $this->safe_list($data, 'contracts', array());
        return $this->parse_margin_modes($marginModes, $symbols, 'symbol');
    }

    public function parse_margin_mode(array $marginMode, $market = null): array {
        $marketId = $this->safe_string($marginMode, 'symbol');
        $marginType = $this->safe_string($marginMode, 'marginType');
        $margin = ($marginType === 'crossed') ? 'cross' : 'isolated';
        return array(
            'info' => $marginMode,
            'symbol' => $this->safe_symbol($marketId, $market),
            'marginMode' => $margin,
        );
    }

    public function fetch_leverages(?array $symbols = null, $params = array ()): array {
        /**
         * fetch the set leverage for all contract markets
         *
         * @see https://ascendex.github.io/ascendex-futures-pro-api-v2/#position
         *
         * @param {string[]} [$symbols] a 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=leverage-structure leverage structures~
         */
        $this->load_markets();
        $this->load_accounts();
        $account = $this->safe_dict($this->accounts, 0, array());
        $accountGroup = $this->safe_string($account, 'id');
        $request = array(
            'account-group' => $accountGroup,
        );
        $response = $this->v2PrivateAccountGroupGetFuturesPosition ($this->extend($request, $params));
        //
        //     {
        //         "code" => 0,
        //         "data" => {
        //             "accountId" => "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
        //             "ac" => "FUTURES",
        //             "collaterals" => array(
        //                 {
        //                     "asset" => "USDT",
        //                     "balance" => "44.570287262",
        //                     "referencePrice" => "1",
        //                     "discountFactor" => "1"
        //                 }
        //             ),
        //             "contracts" => array(
        //                 array(
        //                     "symbol" => "BTC-PERP",
        //                     "side" => "LONG",
        //                     "position" => "0.0001",
        //                     "referenceCost" => "-3.12277254",
        //                     "unrealizedPnl" => "-0.001700233",
        //                     "realizedPnl" => "0",
        //                     "avgOpenPrice" => "31209",
        //                     "marginType" => "isolated",
        //                     "isolatedMargin" => "1.654972977",
        //                     "leverage" => "2",
        //                     "takeProfitPrice" => "0",
        //                     "takeProfitTrigger" => "market",
        //                     "stopLossPrice" => "0",
        //                     "stopLossTrigger" => "market",
        //                     "buyOpenOrderNotional" => "0",
        //                     "sellOpenOrderNotional" => "0",
        //                     "markPrice" => "31210.723063672",
        //                     "indexPrice" => "31223.148857925"
        //                 ),
        //             )
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        $leverages = $this->safe_list($data, 'contracts', array());
        return $this->parse_leverages($leverages, $symbols, 'symbol');
    }

    public function parse_leverage(array $leverage, ?array $market = null): array {
        $marketId = $this->safe_string($leverage, 'symbol');
        $leverageValue = $this->safe_integer($leverage, 'leverage');
        $marginType = $this->safe_string($leverage, 'marginType');
        $marginMode = ($marginType === 'crossed') ? 'cross' : 'isolated';
        return array(
            'info' => $leverage,
            'symbol' => $this->safe_symbol($marketId, $market),
            'marginMode' => $marginMode,
            'longLeverage' => $leverageValue,
            'shortLeverage' => $leverageValue,
        );
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $version = $api[0];
        $access = $api[1];
        $type = $this->safe_string($api, 2);
        $url = '';
        $accountCategory = ($type === 'accountCategory');
        if ($accountCategory || ($type === 'accountGroup')) {
            $url .= $this->implode_params('/{account-group}', $params);
            $params = $this->omit($params, 'account-group');
        }
        $request = $this->implode_params($path, $params);
        $url .= '/api/pro/';
        if ($version === 'v2') {
            if ($type === 'data') {
                $request = 'data/' . $version . '/' . $request;
            } else {
                $request = $version . '/' . $request;
            }
        } else {
            $url .= $version . '/';
        }
        if ($accountCategory) {
            $url .= $this->implode_params('{account-category}/', $params);
        }
        $params = $this->omit($params, 'account-category');
        $url .= $request;
        if (($version === 'v1') && ($request === 'cash/balance') || ($request === 'margin/balance')) {
            $request = 'balance';
        }
        if (($version === 'v1') && ($request === 'spot/fee')) {
            $request = 'fee';
        }
        if (mb_strpos($request, 'subuser') !== false) {
            $parts = explode('/', $request);
            $request = $parts[2];
        }
        $params = $this->omit($params, $this->extract_params($path));
        if ($access === 'public') {
            if ($params) {
                $url .= '?' . $this->urlencode($params);
            }
        } else {
            $this->check_required_credentials();
            $timestamp = (string) $this->milliseconds();
            $payload = $timestamp . '+' . $request;
            $hmac = $this->hmac($this->encode($payload), $this->encode($this->secret), 'sha256', 'base64');
            $headers = array(
                'x-auth-key' => $this->apiKey,
                'x-auth-timestamp' => $timestamp,
                'x-auth-signature' => $hmac,
            );
            if ($method === 'GET') {
                if ($params) {
                    $url .= '?' . $this->urlencode($params);
                }
            } else {
                $headers['Content-Type'] = 'application/json';
                $body = $this->json($params);
            }
        }
        $url = $this->urls['api']['rest'] . $url;
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function handle_errors(int $httpCode, string $reason, string $url, string $method, array $headers, string $body, $response, $requestHeaders, $requestBody) {
        if ($response === null) {
            return null; // fallback to default $error handler
        }
        //
        //     array("code" => 6010, "message" => "Not enough balance.")
        //     array("code" => 60060, "message" => "The order is already filled or canceled.")
        //     array("code":2100,"message":"ApiKeyFailure")
        //     array("code":300001,"message":"Price is too low from market price.","reason":"INVALID_PRICE","accountId":"cshrHKLZCjlZ2ejqkmvIHHtPmLYqdnda","ac":"CASH","action":"place-order","status":"Err","info":array("symbol":"BTC/USDT"))
        //
        $code = $this->safe_string($response, 'code');
        $message = $this->safe_string($response, 'message');
        $error = ($code !== null) && ($code !== '0');
        if ($error || ($message !== null)) {
            $feedback = $this->id . ' ' . $body;
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $code, $feedback);
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $message, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $message, $feedback);
            throw new ExchangeError($feedback); // unknown $message
        }
        return null;
    }
}
