<?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\coincatch as Exchange;

class coincatch extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'coincatch',
            'name' => 'CoinCatch',
            'countries' => array( 'VG' ), // British Virgin Islands
            'rateLimit' => 50, // 20 times per second
            'version' => 'v1',
            'certified' => false,
            'pro' => true,
            'has' => array(
                'CORS' => null,
                'spot' => true,
                'margin' => false,
                'swap' => true,
                'future' => false,
                'option' => false,
                'addMargin' => true,
                'cancelAllOrders' => true,
                'cancelAllOrdersAfter' => false,
                'cancelOrder' => true,
                'cancelOrders' => true,
                'cancelWithdraw' => false,
                'closePosition' => false,
                'createConvertTrade' => false,
                'createDepositAddress' => false,
                'createLimitBuyOrder' => true,
                'createLimitSellOrder' => true,
                'createMarketBuyOrder' => true,
                'createMarketBuyOrderWithCost' => true,
                'createMarketOrder' => true,
                'createMarketOrderWithCost' => false,
                'createMarketSellOrder' => true,
                'createMarketSellOrderWithCost' => false,
                'createOrder' => true,
                'createOrders' => true,
                'createOrderWithTakeProfitAndStopLoss' => true,
                'createPostOnlyOrder' => true,
                'createReduceOnlyOrder' => true,
                'createStopLimitOrder' => true,
                'createStopLossOrder' => true,
                'createStopMarketOrder' => true,
                'createStopOrder' => true,
                'createTakeProfitOrder' => true,
                'createTrailingAmountOrder' => false,
                'createTrailingPercentOrder' => false,
                'createTriggerOrder' => true,
                'fetchAccounts' => false,
                'fetchBalance' => true,
                'fetchCanceledAndClosedOrders' => true,
                'fetchCanceledOrders' => false,
                'fetchClosedOrder' => false,
                'fetchClosedOrders' => false,
                'fetchConvertCurrencies' => false,
                'fetchConvertQuote' => false,
                'fetchConvertTrade' => false,
                'fetchConvertTradeHistory' => false,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => true,
                'fetchDeposits' => true,
                'fetchDepositsWithdrawals' => false,
                'fetchDepositWithdrawFees' => true,
                'fetchFundingHistory' => false,
                'fetchFundingRate' => true,
                'fetchFundingRateHistory' => true,
                'fetchFundingRates' => false,
                'fetchIndexOHLCV' => false,
                'fetchLedger' => true,
                'fetchLeverage' => true,
                'fetchLeverageTiers' => false,
                'fetchMarginAdjustmentHistory' => false,
                'fetchMarginMode' => true,
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => true,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenInterestHistory' => false,
                'fetchOpenOrder' => false,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => false,
                'fetchOrderTrades' => true,
                'fetchPosition' => true,
                'fetchPositionHistory' => false,
                'fetchPositionMode' => true,
                'fetchPositions' => true,
                'fetchPositionsForSymbol' => true,
                'fetchPositionsHistory' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchStatus' => false,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => false,
                'fetchTradingFees' => false,
                'fetchTransactions' => false,
                'fetchTransfers' => false,
                'fetchWithdrawals' => true,
                'reduceMargin' => true,
                'sandbox' => false,
                'setLeverage' => true,
                'setMargin' => false,
                'setMarginMode' => true,
                'setPositionMode' => true,
                'transfer' => false,
                'withdraw' => true,
            ),
            'timeframes' => array(
                '1m' => '1m',
                '3m' => '3m',
                '5m' => '5m',
                '15' => '15m',
                '30' => '30m',
                '1h' => '1H',
                '2h' => '2H',
                '4h' => '4H',
                '6h' => '6H',
                '12h' => '12H',
                '1d' => '1D',
                '3d' => '3D',
                '1w' => '1W',
                '1M' => '1M',
            ),
            'urls' => array(
                'logo' => 'https://github.com/user-attachments/assets/3d49065f-f05d-4573-88a2-1b5201ec6ff3',
                'api' => array(
                    'public' => 'https://api.coincatch.com',
                    'private' => 'https://api.coincatch.com',
                ),
                'www' => 'https://www.coincatch.com/',
                'doc' => 'https://coincatch.github.io/github.io/en/',
                'fees' => 'https://www.coincatch.com/en/rate/',
                'referral' => array(
                    'url' => 'https://partner.coincatch.cc/bg/92hy70391729607848548',
                    'discount' => 0.1,
                ),
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'api/spot/v1/public/time' => 1, // done
                        'api/spot/v1/public/currencies' => 20 / 3, // done
                        'api/spot/v1/market/ticker' => 1, // done
                        'api/spot/v1/market/tickers' => 1, // done
                        'api/spot/v1/market/fills' => 2, // not used
                        'api/spot/v1/market/fills-history' => 2, // done
                        'api/spot/v1/market/candles' => 1, // done
                        'api/spot/v1/market/history-candles' => 1, // not used
                        'api/spot/v1/market/depth' => 1, // not used
                        'api/spot/v1/market/merge-depth' => 1, // done
                        'api/mix/v1/market/contracts' => 1, // done
                        'api/mix/v1/market/merge-depth' => 1, // done
                        'api/mix/v1/market/depth' => 1, // not used
                        'api/mix/v1/market/ticker' => 1, // done
                        'api/mix/v1/market/tickers' => 1, // done
                        'api/mix/v1/market/fills' => 1, // not used
                        'api/mix/v1/market/fills-history' => 1, // done
                        'api/mix/v1/market/candles' => 1, // done
                        'pi/mix/v1/market/index' => 1,
                        'api/mix/v1/market/funding-time' => 1,
                        'api/mix/v1/market/history-fundRate' => 1, // done
                        'api/mix/v1/market/current-fundRate' => 1, // done
                        'api/mix/v1/market/open-interest' => 1,
                        'api/mix/v1/market/mark-price' => 1,
                        'api/mix/v1/market/symbol-leverage' => 1, // done
                        'api/mix/v1/market/queryPositionLever' => 1,
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'api/spot/v1/wallet/deposit-address' => 4, // done
                        'pi/spot/v1/wallet/withdrawal-list' => 1, // not used
                        'api/spot/v1/wallet/withdrawal-list-v2' => 1, // done but should be checked
                        'api/spot/v1/wallet/deposit-list' => 1, // done
                        'api/spot/v1/account/getInfo' => 1,
                        'api/spot/v1/account/assets' => 2, // done
                        'api/spot/v1/account/transferRecords' => 1,
                        'api/mix/v1/account/account' => 2, // done
                        'api/mix/v1/account/accounts' => 2, // done
                        'api/mix/v1/position/singlePosition-v2' => 2, // done
                        'api/mix/v1/position/allPosition-v2' => 4, // done
                        'api/mix/v1/account/accountBill' => 2,
                        'api/mix/v1/account/accountBusinessBill' => 4,
                        'api/mix/v1/order/current' => 1, // done
                        'api/mix/v1/order/marginCoinCurrent' => 1, // done
                        'api/mix/v1/order/history' => 2, // done
                        'api/mix/v1/order/historyProductType' => 4, // done
                        'api/mix/v1/order/detail' => 2, // done
                        'api/mix/v1/order/fills' => 2, // done
                        'api/mix/v1/order/allFills' => 2, // done
                        'api/mix/v1/plan/currentPlan' => 1, // done
                        'api/mix/v1/plan/historyPlan' => 2, // done
                    ),
                    'post' => array(
                        'api/spot/v1/wallet/transfer-v2' => 4, // done
                        'api/spot/v1/wallet/withdrawal-v2' => 4, // done but should be checked
                        'api/spot/v1/wallet/withdrawal-inner-v2' => 1,
                        'api/spot/v1/account/bills' => 2, // done
                        'api/spot/v1/trade/orders' => 2, // done
                        'api/spot/v1/trade/batch-orders' => array( 'cost' => 4, 'step' => 10 ), // done
                        'api/spot/v1/trade/cancel-order' => 1, // not used
                        'api/spot/v1/trade/cancel-order-v2' => 2, // done
                        'api/spot/v1/trade/cancel-symbol-order' => 2, // done
                        'api/spot/v1/trade/cancel-batch-orders' => 1, // not used
                        'api/spot/v1/trade/cancel-batch-orders-v2' => 1, // done
                        'api/spot/v1/trade/orderInfo' => 1, // done
                        'api/spot/v1/trade/open-orders' => 1, // done
                        'api/spot/v1/trade/history' => 1, // done
                        'api/spot/v1/trade/fills' => 1, // done
                        'api/spot/v1/plan/placePlan' => 1, // done
                        'api/spot/v1/plan/modifyPlan' => 1, // done
                        'api/spot/v1/plan/cancelPlan' => 1, // done
                        'api/spot/v1/plan/currentPlan' => 1, // done
                        'api/spot/v1/plan/historyPlan' => 1, // done
                        'api/spot/v1/plan/batchCancelPlan' => 2, // done
                        'api/mix/v1/account/open-count' => 1,
                        'api/mix/v1/account/setLeverage' => 4, // done
                        'api/mix/v1/account/setMargin' => 4, // done
                        'api/mix/v1/account/setMarginMode' => 4, // done
                        'api/mix/v1/account/setPositionMode' => 4, // done
                        'api/mix/v1/order/placeOrder' => 2, // done
                        'api/mix/v1/order/batch-orders' => array( 'cost' => 4, 'step' => 10 ), // done
                        'api/mix/v1/order/cancel-order' => 2, // done
                        'api/mix/v1/order/cancel-batch-orders' => 2, // done
                        'api/mix/v1/order/cancel-symbol-orders' => 2, // done
                        'api/mix/v1/order/cancel-all-orders' => 2, // done
                        'api/mix/v1/plan/placePlan' => 2, // done
                        'api/mix/v1/plan/modifyPlan' => 2,
                        'api/mix/v1/plan/modifyPlanPreset' => 2,
                        'api/mix/v1/plan/placeTPSL' => 2, // done
                        'api/mix/v1/plan/placeTrailStop' => 2, // not used
                        'api/mix/v1/plan/placePositionsTPSL' => 2, // not used
                        'api/mix/v1/plan/modifyTPSLPlan' => 2,
                        'api/mix/v1/plan/cancelPlan' => 2, // done
                        'api/mix/v1/plan/cancelSymbolPlan' => 2, // done
                        'api/mix/v1/plan/cancelAllPlan' => 2, // done
                    ),
                ),
            ),
            'requiredCredentials' => array(
                'apiKey' => true,
                'secret' => true,
                'password' => true,
            ),
            'fees' => array(
                'trading' => array(
                    'spot' => array(
                        'tierBased' => false,
                        'percentage' => true,
                        'feeSide' => 'get',
                        'maker' => $this->parse_number('0.001'),
                        'taker' => $this->parse_number('0.001'),
                    ),
                ),
            ),
            'options' => array(
                'brokerId' => '47cfy',
                'createMarketBuyOrderRequiresPrice' => true, // for spot orders only
                'timeframes' => array(
                    'spot' => array(
                        '1m' => '1min',
                        '5m' => '5min',
                        '15m' => '15min',
                        '30m' => '30min',
                        '1h' => '1h',
                        '4h' => '4h',
                        '6h' => '6h',
                        '12h' => '12h',
                        '1d' => '1day',
                        '3d' => '3day',
                        '1w' => '1week',
                        '1M' => '1M',
                    ),
                    'swap' => array(
                        '1m' => '1m',
                        '3m' => '3m',
                        '5m' => '5m',
                        '15' => '15m',
                        '30' => '30m',
                        '1h' => '1H',
                        '2h' => '2H',
                        '4h' => '4H',
                        '6h' => '6H',
                        '12h' => '12H',
                        '1d' => '1D',
                        '3d' => '3D',
                        '1w' => '1W',
                        '1M' => '1M',
                    ),
                ),
                'currencyIdsListForParseMarket' => null,
                'broker' => '',
                'networks' => array(
                    'BTC' => 'BITCOIN',
                    'ERC20' => 'ERC20',
                    'TRC20' => 'TRC20',
                    'BEP20' => 'BEP20',
                    'ARB' => 'ArbitrumOne',
                    'OPTIMISM' => 'Optimism',
                    'LTC' => 'LTC',
                    'BCH' => 'BCH',
                    'ETC' => 'ETC',
                    'SOL' => 'SOL',
                    'NEO3' => 'NEO3',
                    'STX' => 'stacks',
                    'EGLD' => 'Elrond',
                    'NEAR' => 'NEARProtocol',
                    'ACA' => 'AcalaToken',
                    'KLAY' => 'Klaytn',
                    'FTM' => 'Fantom',
                    'TERRA' => 'Terra',
                    'WAVES' => 'WAVES',
                    'TAO' => 'TAO',
                    'SUI' => 'SUI',
                    'SEI' => 'SEI',
                    'RUNE' => 'THORChain',
                    'ZIL' => 'ZIL',
                    'SXP' => 'Solar',
                    'FET' => 'FET',
                    'AVAX' => 'C-Chain',
                    'XRP' => 'XRP',
                    'EOS' => 'EOS',
                    'DOGE' => 'DOGECOIN',
                    'CAP20' => 'CAP20',
                    'MATIC' => 'Polygon',
                    'CSPR' => 'CSPR',
                    'GLMR' => 'Moonbeam',
                    'MINA' => 'MINA',
                    'CFX' => 'CFX',
                    'STRAT' => 'StratisEVM',
                    'TIA' => 'Celestia',
                    'ChilizChain' => 'ChilizChain',
                    'APT' => 'Aptos',
                    'ONT' => 'Ontology',
                    'ICP' => 'ICP',
                    'ADA' => 'Cardano',
                    'FIL' => 'FIL',
                    'CELO' => 'CELO',
                    'DOT' => 'DOT',
                    'XLM' => 'StellarLumens',
                    'ATOM' => 'ATOM',
                    'CRO' => 'CronosChain',
                ),
                'networksById' => array(
                    'TRC20' => 'TRC20',
                    'TRX(TRC20)' => 'TRC20',
                    'ArbitrumOne' => 'ARB', // todo check
                    'THORChain' => 'RUNE', // todo check
                    'Solar' => 'SXP', // todo check
                    'C-Chain' => 'AVAX', // todo check
                    'CAP20' => 'CAP20', // todo check
                    'CFXeSpace' => 'CFX', // todo check
                    'CFX' => 'CFX',
                    'StratisEVM' => 'STRAT', // todo check
                    'ChilizChain' => 'ChilizChain', // todo check
                    'StellarLumens' => 'XLM', // todo check
                    'CronosChain' => 'CRO', // todo check
                ),
            ),
            'features' => array(
                'default' => array(
                    'sandbox' => false,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => true,
                        'triggerPriceType' => array(
                            'last' => true,
                            'mark' => true,
                            'index' => false,
                        ),
                        'triggerDirection' => false,
                        'stopLossPrice' => false, // todo
                        'takeProfitPrice' => false, // todo
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => true,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'trailing' => false,
                        'leverage' => false,
                        'marketBuyByCost' => true,
                        'marketBuyRequiresPrice' => false,
                        'selfTradePrevention' => false,
                        'iceberg' => false,
                    ),
                    'createOrders' => array(
                        'max' => 50,
                    ),
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 500,
                        'daysBack' => 100000, // todo implement
                        'untilDays' => 100000, // todo implement
                        'symbolRequired' => true,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => 100,
                        'trigger' => true,
                        'trailing' => false,
                        'marketType' => true,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => null,
                    'fetchClosedOrders' => null, // todo implement
                    'fetchOHLCV' => array(
                        'limit' => 1000,
                    ),
                ),
                'spot' => array(
                    'extends' => 'default',
                ),
                'forDerivatives' => array(
                    'extends' => 'default',
                    'createOrder' => array(
                        // todo check
                        'attachedStopLossTakeProfit' => array(
                            'triggerPriceType' => null,
                            'price' => false,
                        ),
                    ),
                    'fetchMyTrades' => array(
                        'limit' => 100,
                    ),
                ),
                'swap' => array(
                    'linear' => array(
                        'extends' => 'forDerivatives',
                    ),
                    'inverse' => array(
                        'extends' => 'forDerivatives',
                    ),
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
            'commonCurrencies' => array(),
            'exceptions' => array(
                'exact' => array(
                    '22001' => '\\ccxt\\OrderNotFound', // No order to cancel
                    '429' => '\\ccxt\\DDoSProtection', // Request is too frequent
                    '40001' => '\\ccxt\\AuthenticationError', // The request header "ACCESS_KEY" cannot be empty
                    '40002' => '\\ccxt\\AuthenticationError', // The request header "ACCESS_SIGN" cannot be empty
                    '40003' => '\\ccxt\\AuthenticationError', // The request header "ACCESS_TIMESTAMP" cannot be empty
                    '40005' => '\\ccxt\\InvalidNonce', // Invalid ACCESS_TIMESTAMP
                    '40006' => '\\ccxt\\AuthenticationError', // Invalid ACCESS_KEY
                    '40007' => '\\ccxt\\BadRequest', // Invalid Content_Type，please use“application/json”format
                    '40008' => '\\ccxt\\InvalidNonce', // Request timestamp expired
                    '40009' => '\\ccxt\\AuthenticationError', // api verification failed
                    '40011' => '\\ccxt\\AuthenticationError', // The request header "ACCESS_PASSPHRASE" cannot be empty
                    '40012' => '\\ccxt\\AuthenticationError', // apikey/passphrase is incorrect
                    '40013' => '\\ccxt\\ExchangeError', // User has been frozen
                    '40014' => '\\ccxt\\PermissionDenied', // Incorrect permissions
                    '40015' => '\\ccxt\\ExchangeError', // System error
                    '40016' => '\\ccxt\\PermissionDenied', // The user must bind a mobile phone or Google authenticator
                    '40017' => '\\ccxt\\ExchangeError', // Parameter verification failed
                    '40018' => '\\ccxt\\PermissionDenied', // Illegal IP request
                    '40019' => '\\ccxt\\BadRequest', // Parameter {0} cannot be empty
                    '40020' => '\\ccxt\\BadRequest', // Parameter orderIds or clientOids error
                    '40034' => '\\ccxt\\BadRequest', // Parameter {0} does not exist
                    '400172' => '\\ccxt\\BadRequest', // symbol cannot be empty
                    '40912' => '\\ccxt\\BadRequest', // Batch processing orders can only process up to 50
                    '40913' => '\\ccxt\\BadRequest', // orderId or clientOrderId must be passed one
                    '40102' => '\\ccxt\\BadRequest', // The contract configuration does not exist, please check the parameters
                    '40200' => '\\ccxt\\OnMaintenance', // Server upgrade, please try again later
                    '40305' => '\\ccxt\\BadRequest', // client_oid length is not greater than 40, and cannot be Martian characters
                    '40409' => '\\ccxt\\ExchangeError', // wrong format
                    '40704' => '\\ccxt\\ExchangeError', // Only check the data of the last three months
                    '40724' => '\\ccxt\\BadRequest', // Parameter is empty
                    '40725' => '\\ccxt\\ExchangeError', // spot service return an error
                    '40762' => '\\ccxt\\InsufficientFunds', // The order amount exceeds the balance
                    '40774' => '\\ccxt\\BadRequest', // The order type for unilateral position must also be the unilateral position type.
                    '40808' => '\\ccxt\\BadRequest', // Parameter verification exception {0}
                    '43001' => '\\ccxt\\OrderNotFound', // The order does not exist
                    '43002' => '\\ccxt\\InvalidOrder', // Pending order failed
                    '43004' => '\\ccxt\\OrderNotFound', // There is no order to cancel
                    '43005' => '\\ccxt\\RateLimitExceeded', // Exceeded the maximum order limit of transaction volume
                    '43006' => '\\ccxt\\BadRequest', // The order quantity is less than the minimum transaction quantity
                    '43007' => '\\ccxt\\BadRequest', // The order quantity is greater than the maximum transaction quantity
                    '43008' => '\\ccxt\\BadRequest', // The current order price cannot be less than {0}
                    '43009' => '\\ccxt\\BadRequest', // The current commission price exceeds the limit {0}
                    '43010' => '\\ccxt\\BadRequest', // The transaction amount cannot be less than {0}
                    '43011' => '\\ccxt\\BadRequest', // The current order price cannot be less than {0}
                    '43012' => '\\ccxt\\InsufficientFunds', // array("code":"43012","msg":"Insufficient balance","requestTime":1729327822139,"data":null)
                    '43117' => '\\ccxt\\InsufficientFunds', // Exceeds the maximum amount that can be transferred
                    '43118' => '\\ccxt\\BadRequest', // clientOrderId duplicate
                    '43122' => '\\ccxt\\BadRequest', // The purchase limit of this currency is {0}, and there is still {1} left
                    '45006' => '\\ccxt\\InsufficientFunds', // Insufficient position
                    '45110' => '\\ccxt\\BadRequest', // less than the minimum amount {0} {1}
                    // array("code":"40913","msg":"orderId or clientOrderId must be passed one","requestTime":1726160988275,"data":null)
                ),
                'broad' => array(),
            ),
            'precisionMode' => TICK_SIZE,
        ));
    }

    public function calculate_rate_limiter_cost($api, $method, $path, $params, $config = array ()) {
        $step = $this->safe_integer($config, 'step');
        $cost = $this->safe_integer($config, 'cost', 1);
        $orders = $this->safe_list_2($params, 'orderList', 'orderDataList', array());
        $ordersLength = count($orders);
        if (($step !== null) && ($ordersLength > $step)) {
            $numberOfSteps = (int) ceil($ordersLength / $step);
            return $cost * $numberOfSteps;
        } else {
            return $cost;
        }
    }

    public function fetch_time($params = array ()): ?int {
        /**
         * fetches the current integer timestamp in milliseconds from the exchange server
         *
         * @see https://coincatch.github.io/github.io/en/spot/#get-server-time
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {int} the current integer timestamp in milliseconds from the exchange server
         */
        $response = $this->publicGetApiSpotV1PublicTime ($params);
        //
        //     {
        //         "code" => "00000",
        //         "msg" => "success",
        //         "requestTime" => 1725046822028,
        //         "data" => "1725046822028"
        //     }
        //
        return $this->safe_integer($response, 'data');
    }

    public function fetch_currencies($params = array ()): ?array {
        /**
         * fetches all available currencies on an exchange
         *
         * @see https://coincatch.github.io/github.io/en/spot/#get-coin-list
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an associative dictionary of currencies
         */
        $response = $this->publicGetApiSpotV1PublicCurrencies ($params);
        $data = $this->safe_list($response, 'data', array());
        //
        //     {
        //         "code" => "00000",
        //         "msg" => "success",
        //         "requestTime" => 1725102364202,
        //         "data" => array(
        //             {
        //                 "coinId" => "1",
        //                 "coinName" => "BTC",
        //                 "transfer" => "true",
        //                 "chains" => array(
        //                     array(
        //                         "chainId" => "10",
        //                         "chain" => "BITCOIN",
        //                         "needTag" => "false",
        //                         "withdrawable" => "true",
        //                         "rechargeable" => "true",
        //                         "withdrawFee" => "0.0005",
        //                         "extraWithDrawFee" => "0",
        //                         "depositConfirm" => "1",
        //                         "withdrawConfirm" => "1",
        //                         "minDepositAmount" => "0.00001",
        //                         "minWithdrawAmount" => "0.001",
        //                         "browserUrl" => "https://blockchair.com/bitcoin/transaction/"
        //                     }
        //                 )
        //             ),
        //             ...
        //         )
        //     }
        //
        $result = array();
        $currenciesIds = array();
        for ($i = 0; $i < count($data); $i++) {
            $currecy = $data[$i];
            $currencyId = $this->safe_string($currecy, 'coinName');
            $currenciesIds[] = $currencyId;
            $code = $this->safe_currency_code($currencyId);
            $networks = $this->safe_list($currecy, 'chains');
            $parsedNetworks = array();
            for ($j = 0; $j < count($networks); $j++) {
                $network = $networks[$j];
                $networkId = $this->safe_string($network, 'chain');
                $networkCode = $this->network_id_to_code($networkId);
                $parsedNetworks[$networkCode] = array(
                    'id' => $networkId,
                    'network' => $networkCode,
                    'limits' => array(
                        'deposit' => array(
                            'min' => $this->safe_number($network, 'minDepositAmount'),
                            'max' => null,
                        ),
                        'withdraw' => array(
                            'min' => $this->safe_number($network, 'minWithdrawAmount'),
                            'max' => null,
                        ),
                    ),
                    'active' => null,
                    'deposit' => $this->safe_string($network, 'rechargeable') === 'true',
                    'withdraw' => $this->safe_string($network, 'withdrawable') === 'true',
                    'fee' => $this->safe_number($network, 'withdrawFee'),
                    'precision' => null,
                    'info' => $network,
                );
            }
            $result[$code] = $this->safe_currency_structure(array(
                'id' => $currencyId,
                'numericId' => $this->safe_integer($currecy, 'coinId'),
                'code' => $code,
                'precision' => null,
                'type' => null,
                'name' => null,
                'active' => null,
                'deposit' => null,
                'withdraw' => null,
                'fee' => null,
                'limits' => array(
                    'deposit' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'withdraw' => array(
                        'min' => null,
                        'max' => null,
                    ),
                ),
                'networks' => $parsedNetworks,
                'info' => $currecy,
            ));
        }
        if ($this->safe_list($this->options, 'currencyIdsListForParseMarket') === null) {
            $this->options['currencyIdsListForParseMarket'] = $currenciesIds;
        }
        return $result;
    }

    public function fetch_deposit_withdraw_fees(?array $codes = null, $params = array ()) {
        /**
         * fetch deposit and withdraw fees
         *
         * @see https://coincatch.github.io/github.io/en/spot/#get-coin-list
         *
         * @param {string[]} [$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->publicGetApiSpotV1PublicCurrencies ($params);
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_deposit_withdraw_fees($data, $codes, 'coinName');
    }

    public function parse_deposit_withdraw_fee($fee, ?array $currency = null) {
        //
        // {
        //     "coinId":"1",
        //     "coinName":"BTC",
        //     "transfer":"true",
        //     "chains":array(
        //         {
        //             "chain":null,
        //             "needTag":"false",
        //             "withdrawable":"true",
        //             "rechargeAble":"true",
        //             "withdrawFee":"0.005",
        //             "depositConfirm":"1",
        //             "withdrawConfirm":"1",
        //             "minDepositAmount":"0.001",
        //             "minWithdrawAmount":"0.001",
        //             "browserUrl":"https://blockchair.com/bitcoin/testnet/transaction/"
        //         }
        //     )
        // }
        //
        $chains = $this->safe_list($fee, 'chains', array());
        $chainsLength = count($chains);
        $result = array(
            'info' => $fee,
            'withdraw' => array(
                'fee' => null,
                'percentage' => null,
            ),
            'deposit' => array(
                'fee' => null,
                'percentage' => null,
            ),
            'networks' => array(),
        );
        for ($i = 0; $i < $chainsLength; $i++) {
            $chain = $chains[$i];
            $networkId = $this->safe_string($chain, 'chain');
            $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($chain, 'withdrawFee'), 'percentage' => false ),
            );
            if ($chainsLength === 1) {
                $result['withdraw']['fee'] = $this->safe_number($chain, 'withdrawFee');
                $result['withdraw']['percentage'] = false;
            }
        }
        return $result;
    }

    public function fetch_markets($params = array ()): array {
        /**
         * retrieves data on all $markets for the exchange
         *
         * @see https://coincatch.github.io/github.io/en/spot/#get-all-tickers
         * @see https://coincatch.github.io/github.io/en/mix/#get-all-symbols
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing $market data
         */
        $response = $this->publicGetApiSpotV1MarketTickers ($params);
        //
        //     {
        //         "code" => "00000",
        //         "msg" => "success",
        //         "requestTime" => 1725114040155,
        //         "data" => array(
        //             array(
        //                 "symbol" => "BTCUSDT",
        //                 "high24h" => "59461.34",
        //                 "low24h" => "57723.23",
        //                 "close" => "59056.02",
        //                 "quoteVol" => "18240112.23368",
        //                 "baseVol" => "309.05564",
        //                 "usdtVol" => "18240112.2336744",
        //                 "ts" => "1725114038951",
        //                 "buyOne" => "59055.85",
        //                 "sellOne" => "59057.45",
        //                 "bidSz" => "0.0139",
        //                 "askSz" => "0.0139",
        //                 "openUtc0" => "59126.71",
        //                 "changeUtc" => "-0.0012",
        //                 "change" => "0.01662"
        //             ),
        //             ...
        //         )
        //     }
        //
        if ($this->safe_list($this->options, 'currencyIdsListForParseMarket') === null) {
            $this->fetch_currencies();
        }
        $spotMarkets = $this->safe_list($response, 'data', array());
        $request = array();
        $productType = null;
        list($productType, $params) = $this->handle_option_and_params($params, 'fetchMarkets', 'productType', $productType);
        $swapMarkets = array();
        $request['productType'] = 'umcbl';
        $response = $this->publicGetApiMixV1MarketContracts ($this->extend($request, $params));
        //
        //     {
        //         "code" => "00000",
        //         "msg" => "success",
        //         "requestTime" => 1725297439225,
        //         "data" => array(
        //             {
        //                 "symbol" => "BTCUSDT_UMCBL",
        //                 "makerFeeRate" => "0.0002",
        //                 "takerFeeRate" => "0.0006",
        //                 "feeRateUpRatio" => "0.005",
        //                 "openCostUpRatio" => "0.01",
        //                 "quoteCoin" => "USDT",
        //                 "baseCoin" => "BTC",
        //                 "buyLimitPriceRatio" => "0.01",
        //                 "sellLimitPriceRatio" => "0.01",
        //                 "supportMarginCoins" => array( "USDT" ),
        //                 "minTradeNum" => "0.001",
        //                 "priceEndStep" => "1",
        //                 "volumePlace" => "3",
        //                 "pricePlace" => "1",
        //                 "sizeMultiplier" => "0.001",
        //                 "symbolType" => "perpetual",
        //                 "symbolStatus" => "normal",
        //                 "offTime" => "-1",
        //                 "limitOpenTime" => "-1",
        //                 "maintainTime" => "",
        //                 "symbolName" => "BTCUSDT",
        //                 "minTradeUSDT" => null,
        //                 "maxPositionNum" => null,
        //                 "maxOrderNum" => null
        //             }
        //         )
        //     }
        //
        $swapUMCBL = $this->safe_list($response, 'data', array());
        $request['productType'] = 'dmcbl';
        $response = $this->publicGetApiMixV1MarketContracts ($this->extend($request, $params));
        //
        //     {
        //         "code":"00000",
        //         "msg":"success",
        //         "requestTime":1725297439646,
        //         "data":array(
        //             {
        //                 "symbol":"BTCUSD_DMCBL",
        //                 "makerFeeRate":"0.0002",
        //                 "takerFeeRate":"0.0006",
        //                 "feeRateUpRatio":"0.005",
        //                 "openCostUpRatio":"0.01",
        //                 "quoteCoin":"USD",
        //                 "baseCoin":"BTC",
        //                 "buyLimitPriceRatio":"0.01",
        //                 "sellLimitPriceRatio":"0.01",
        //                 "supportMarginCoins":array(
        //                     "BTC",
        //                     "ETH"
        //                 ),
        //                 "minTradeNum":"0.001",
        //                 "priceEndStep":"1",
        //                 "volumePlace":"3",
        //                 "pricePlace":"1",
        //                 "sizeMultiplier":"0.001",
        //                 "symbolType":"perpetual",
        //                 "symbolStatus":"normal",
        //                 "offTime":"-1",
        //                 "limitOpenTime":"-1",
        //                 "maintainTime":"",
        //                 "symbolName":"BTCUSD",
        //                 "minTradeUSDT":null,
        //                 "maxPositionNum":null,
        //                 "maxOrderNum":null
        //             }
        //         )
        //     }
        $swapDMCBL = $this->safe_list($response, 'data', array());
        $swapDMCBLExtended = array();
        for ($i = 0; $i < count($swapDMCBL); $i++) {
            $market = $swapDMCBL[$i];
            $supportMarginCoins = $this->safe_list($market, 'supportMarginCoins', array());
            for ($j = 0; $j < count($supportMarginCoins); $j++) {
                $settle = $supportMarginCoins[$j];
                $obj = array(
                    'supportMarginCoins' => array( $settle ),
                );
                $swapDMCBLExtended[] = $this->extend($market, $obj);
            }
        }
        $swapMarkets = $this->array_concat($swapUMCBL, $swapDMCBLExtended);
        $markets = $this->array_concat($spotMarkets, $swapMarkets);
        return $this->parse_markets($markets);
    }

    public function parse_market(array $market): array {
        //
        // spot
        //     array(
        //         "symbol" => "BTCUSDT",
        //         "high24h" => "59461.34",
        //         "low24h" => "57723.23",
        //         "close" => "59056.02",
        //         "quoteVol" => "18240112.23368",
        //         "baseVol" => "309.05564",
        //         "usdtVol" => "18240112.2336744",
        //         "ts" => "1725114038951",
        //         "buyOne" => "59055.85",
        //         "sellOne" => "59057.45",
        //         "bidSz" => "0.0139",
        //         "askSz" => "0.0139",
        //         "openUtc0" => "59126.71",
        //         "changeUtc" => "-0.0012",
        //         "change" => "0.01662"
        //     ),
        //
        // swap
        //     {
        //         "symbol" => "BTCUSDT_UMCBL",
        //         "makerFeeRate" => "0.0002",
        //         "takerFeeRate" => "0.0006",
        //         "feeRateUpRatio" => "0.005",
        //         "openCostUpRatio" => "0.01",
        //         "quoteCoin" => "USDT",
        //         "baseCoin" => "BTC",
        //         "buyLimitPriceRatio" => "0.01",
        //         "sellLimitPriceRatio" => "0.01",
        //         "supportMarginCoins" => array( "USDT" ),
        //         "minTradeNum" => "0.001",
        //         "priceEndStep" => "1",
        //         "volumePlace" => "3",
        //         "pricePlace" => "1",
        //         "sizeMultiplier" => "0.001",
        //         "symbolType" => "perpetual",
        //         "symbolStatus" => "normal",
        //         "offTime" => "-1",
        //         "limitOpenTime" => "-1",
        //         "maintainTime" => "",
        //         "symbolName" => "BTCUSDT",
        //         "minTradeUSDT" => null,
        //         "maxPositionNum" => null,
        //         "maxOrderNum" => null
        //     }
        //
        $marketId = $this->safe_string($market, 'symbol');
        $tradingFees = $this->safe_dict($this->fees, 'trading');
        $fees = $this->safe_dict($tradingFees, 'spot');
        $baseId = $this->safe_string($market, 'baseCoin');
        $quoteId = $this->safe_string($market, 'quoteCoin');
        $settleId = null;
        $suffix = '';
        $settle = null;
        $type = 'spot';
        $isLinear = null;
        $isInverse = null;
        $subType = null;
        $isSpot = $baseId === null; // for now spot markets have no properties baseCoin and quoteCoin
        if ($isSpot) {
            $parsedMarketId = $this->parse_spot_market_id($marketId);
            $baseId = $this->safe_string($parsedMarketId, 'baseId');
            $quoteId = $this->safe_string($parsedMarketId, 'quoteId');
            $marketId .= '_SPBL'; // spot markets should have current $suffix
        } else {
            $type = 'swap';
            $fees['taker'] = $this->safe_number($market, 'takerFeeRate');
            $fees['maker'] = $this->safe_number($market, 'makerFeeRate');
            $supportMarginCoins = $this->safe_list($market, 'supportMarginCoins', array());
            $settleId = $this->safe_string($supportMarginCoins, 0);
            $settle = $this->safe_currency_code($settleId);
            $suffix = ':' . $settle;
            $isLinear = $quoteId === $settleId; // todo check
            $isInverse = $baseId === $settleId; // todo check
            if ($isLinear) {
                $subType = 'linear';
            } elseif ($isInverse) {
                $subType = 'inverse';
            }
        }
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $symbol = $base . '/' . $quote . $suffix;
        $symbolStatus = $this->safe_string($market, 'symbolStatus');
        $active = $symbolStatus ? ($symbolStatus === 'normal') : null;
        $volumePlace = $this->safe_string($market, 'volumePlace');
        $amountPrecisionString = $this->parse_precision($volumePlace);
        $pricePlace = $this->safe_string($market, 'pricePlace');
        $priceEndStep = $this->safe_string($market, 'priceEndStep');
        $pricePrecisionString = Precise::string_mul($this->parse_precision($pricePlace), $priceEndStep);
        return $this->safe_market_structure(array(
            'id' => $marketId,
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'active' => $active,
            'type' => $type,
            'subType' => $subType,
            'spot' => $isSpot,
            'margin' => $isSpot ? false : null,
            'swap' => !$isSpot,
            'future' => false,
            'option' => false,
            'contract' => !$isSpot,
            'settle' => $settle,
            'settleId' => $settleId,
            'contractSize' => $this->safe_number($market, 'sizeMultiplier'),
            'linear' => $isLinear,
            'inverse' => $isInverse,
            'taker' => $this->safe_number($fees, 'taker'),
            'maker' => $this->safe_number($fees, 'maker'),
            'percentage' => $this->safe_bool($fees, 'percentage'),
            'tierBased' => $this->safe_bool($fees, 'tierBased'),
            'feeSide' => $this->safe_string($fees, 'feeSide'),
            'expiry' => null,
            'expiryDatetime' => null,
            'strike' => null,
            'optionType' => null,
            'precision' => array(
                'amount' => $this->parse_number($amountPrecisionString),
                'price' => $this->parse_number($pricePrecisionString),
            ),
            'limits' => array(
                'amount' => array(
                    'min' => $this->safe_number($market, 'minTradeNum'),
                    'max' => null,
                ),
                'price' => array(
                    'min' => null,
                    'max' => null,
                ),
                'leverage' => array(
                    'min' => null,
                    'max' => null,
                ),
                'cost' => array(
                    'min' => null,
                    'max' => null,
                ),
            ),
            'created' => null,
            'info' => $market,
        ));
    }

    public function parse_spot_market_id($marketId) {
        $baseId = null;
        $quoteId = null;
        $currencyIds = $this->safe_list($this->options, 'currencyIdsListForParseMarket', array());
        for ($i = 0; $i < count($currencyIds); $i++) {
            $currencyId = $currencyIds[$i];
            $entryIndex = mb_strpos($marketId, $currencyId);
            if ($entryIndex > -1) {
                $restId = str_replace($currencyId, '', $marketId);
                if ($entryIndex === 0) {
                    $baseId = $currencyId;
                    $quoteId = $restId;
                } else {
                    $baseId = $restId;
                    $quoteId = $currencyId;
                }
                break;
            }
        }
        $result = array(
            'baseId' => $baseId,
            'quoteId' => $quoteId,
        );
        return $result;
    }

    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
         *
         * @see https://coincatch.github.io/github.io/en/spot/#get-single-ticker
         * @see https://coincatch.github.io/github.io/en/mix/#get-single-$symbol-ticker
         *
         * @param {string} $symbol unified $symbol of the $market to fetch the ticker for
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = null;
        if ($market['spot']) {
            $response = $this->publicGetApiSpotV1MarketTicker ($this->extend($request, $params));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1725132487751,
            //         "data" => {
            //             "symbol" => "ETHUSDT",
            //             "high24h" => "2533.76",
            //             "low24h" => "2492.72",
            //             "close" => "2499.76",
            //             "quoteVol" => "21457850.7442",
            //             "baseVol" => "8517.1869",
            //             "usdtVol" => "21457850.744163",
            //             "ts" => "1725132487476",
            //             "buyOne" => "2499.75",
            //             "sellOne" => "2499.76",
            //             "bidSz" => "0.5311",
            //             "askSz" => "4.5806",
            //             "openUtc0" => "2525.69",
            //             "changeUtc" => "-0.01027",
            //             "change" => "-0.00772"
            //         }
            //     }
            //
        } elseif ($market['swap']) {
            $response = $this->publicGetApiMixV1MarketTicker ($this->extend($request, $params));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1725316687174,
            //         "data" => {
            //             "symbol" => "ETHUSDT_UMCBL",
            //             "last" => "2540.6",
            //             "bestAsk" => "2540.71",
            //             "bestBid" => "2540.38",
            //             "bidSz" => "12.1",
            //             "askSz" => "20",
            //             "high24h" => "2563.91",
            //             "low24h" => "2398.3",
            //             "timestamp" => "1725316687177",
            //             "priceChangePercent" => "0.01134",
            //             "baseVolume" => "706928.96",
            //             "quoteVolume" => "1756401737.8766",
            //             "usdtVolume" => "1756401737.8766",
            //             "openUtc" => "2424.49",
            //             "chgUtc" => "0.04789",
            //             "indexPrice" => "2541.977142",
            //             "fundingRate" => "0.00006",
            //             "holdingAmount" => "144688.49",
            //             "deliveryStartTime" => null,
            //             "deliveryTime" => null,
            //             "deliveryStatus" => "normal"
            //         }
            //     }
            //
        } else {
            throw new NotSupported($this->id . ' ' . 'fetchTicker() is not supported for ' . $market['type'] . ' type of markets');
        }
        $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://coincatch.github.io/github.io/en/spot/#get-all-tickers
         * @see https://coincatch.github.io/github.io/en/mix/#get-all-symbol-ticker
         *
         * @param {string[]} [$symbols] unified $symbols of the markets to fetch the ticker for, all $market tickers are returned if not assigned
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->type] 'spot' or 'swap' (default 'spot')
         * @param {string} [$params->productType] 'umcbl' or 'dmcbl' (default 'umcbl') - USDT perpetual contract or Universal margin perpetual contract
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structures~
         */
        $methodName = 'fetchTickers';
        $this->load_markets();
        $symbols = $this->market_symbols($symbols, null, true, true);
        $market = $this->get_market_from_symbols($symbols);
        $marketType = 'spot';
        list($marketType, $params) = $this->handle_market_type_and_params($methodName, $market, $params, $marketType);
        $response = null;
        if ($marketType === 'spot') {
            $response = $this->publicGetApiSpotV1MarketTickers ($params);
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1725114040155,
            //         "data" => array(
            //             array(
            //                 "symbol" => "BTCUSDT",
            //                 "high24h" => "59461.34",
            //                 "low24h" => "57723.23",
            //                 "close" => "59056.02",
            //                 "quoteVol" => "18240112.23368",
            //                 "baseVol" => "309.05564",
            //                 "usdtVol" => "18240112.2336744",
            //                 "ts" => "1725114038951",
            //                 "buyOne" => "59055.85",
            //                 "sellOne" => "59057.45",
            //                 "bidSz" => "0.0139",
            //                 "askSz" => "0.0139",
            //                 "openUtc0" => "59126.71",
            //                 "changeUtc" => "-0.0012",
            //                 "change" => "0.01662"
            //             ),
            //             ...
            //         )
            //     }
            //
        } elseif ($marketType === 'swap') {
            $productType = 'umcbl';
            list($productType, $params) = $this->handle_option_and_params($params, $methodName, 'productType', $productType);
            $request = array(
                'productType' => $productType,
            );
            $response = $this->publicGetApiMixV1MarketTickers ($this->extend($request, $params));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1725320291340,
            //         "data" => array(
            //             array(
            //                 "symbol" => "BTCUSDT_UMCBL",
            //                 "last" => "59110.5",
            //                 "bestAsk" => "59113.2",
            //                 "bestBid" => "59109.5",
            //                 "bidSz" => "1.932",
            //                 "askSz" => "0.458",
            //                 "high24h" => "59393.5",
            //                 "low24h" => "57088.5",
            //                 "timestamp" => "1725320291347",
            //                 "priceChangePercent" => "0.01046",
            //                 "baseVolume" => "59667.001",
            //                 "quoteVolume" => "3472522256.9927",
            //                 "usdtVolume" => "3472522256.9927",
            //                 "openUtc" => "57263",
            //                 "chgUtc" => "0.03231",
            //                 "indexPrice" => "59151.25442",
            //                 "fundingRate" => "0.00007",
            //                 "holdingAmount" => "25995.377",
            //                 "deliveryStartTime" => null,
            //                 "deliveryTime" => null,
            //                 "deliveryStatus" => "normal"}
            //             ),
            //             ...
            //         )
            //     }
            //
        } else {
            throw new NotSupported($this->id . ' ' . $methodName . '() is not supported for ' . $marketType . ' type of markets');
        }
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_tickers($data, $symbols);
    }

    public function parse_ticker($ticker, ?array $market = null): array {
        //
        // spot
        //     {
        //         "symbol" => "BTCUSDT",
        //         "high24h" => "59461.34",
        //         "low24h" => "57723.23",
        //         "close" => "59056.02",
        //         "quoteVol" => "18240112.23368",
        //         "baseVol" => "309.05564",
        //         "usdtVol" => "18240112.2336744",
        //         "ts" => "1725114038951",
        //         "buyOne" => "59055.85",
        //         "sellOne" => "59057.45",
        //         "bidSz" => "0.0139",
        //         "askSz" => "0.0139",
        //         "openUtc0" => "59126.71",
        //         "changeUtc" => "-0.0012",
        //         "change" => "0.01662"
        //     }
        //
        // swap
        //     {
        //         "symbol" => "ETHUSDT_UMCBL",
        //         "last" => "2540.6",
        //         "bestAsk" => "2540.71",
        //         "bestBid" => "2540.38",
        //         "bidSz" => "12.1",
        //         "askSz" => "20",
        //         "high24h" => "2563.91",
        //         "low24h" => "2398.3",
        //         "timestamp" => "1725316687177",
        //         "priceChangePercent" => "0.01134",
        //         "baseVolume" => "706928.96",
        //         "quoteVolume" => "1756401737.8766",
        //         "usdtVolume" => "1756401737.8766",
        //         "openUtc" => "2424.49",
        //         "chgUtc" => "0.04789",
        //         "indexPrice" => "2541.977142",
        //         "fundingRate" => "0.00006",
        //         "holdingAmount" => "144688.49",
        //         "deliveryStartTime" => null,
        //         "deliveryTime" => null,
        //         "deliveryStatus" => "normal"
        //     }
        //
        $timestamp = $this->safe_integer_2($ticker, 'ts', 'timestamp');
        $marketId = $this->safe_string($ticker, 'symbol', '');
        if (mb_strpos($marketId, '_') === false) {
            $marketId .= '_SPBL'; // spot markets from tickers endpoints have no suffix specific for $market id
        }
        $market = $this->safe_market_custom($marketId, $market);
        $last = $this->safe_string_2($ticker, 'close', 'last');
        return $this->safe_ticker(array(
            'symbol' => $market['symbol'],
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_string($ticker, 'high24h'),
            'low' => $this->safe_string($ticker, 'low24h'),
            'bid' => $this->safe_string_2($ticker, 'buyOne', 'bestBid'),
            'bidVolume' => $this->safe_string($ticker, 'bidSz'),
            'ask' => $this->safe_string_2($ticker, 'sellOne', 'bestAsk'),
            'askVolume' => $this->safe_string($ticker, 'askSz'),
            'vwap' => null,
            'open' => $this->safe_string_2($ticker, 'openUtc0', 'openUtc'),
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => null,
            'percentage' => Precise::string_mul($this->safe_string_2($ticker, 'changeUtc', 'chgUtc'), '100'),
            'average' => null,
            'baseVolume' => $this->safe_string_2($ticker, 'baseVol', 'baseVolume'),
            'quoteVolume' => $this->safe_string_2($ticker, 'quoteVol', 'quoteVolume'),
            'indexPrice' => $this->safe_string($ticker, 'indexPrice'),
            'markPrice' => null,
            'info' => $ticker,
        ), $market);
    }

    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
         *
         * @see https://coincatch.github.io/github.io/en/spot/#get-merged-depth-$data
         * @see https://coincatch.github.io/github.io/en/mix/#get-merged-depth-$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 (maximum and default value is 100)
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->precision] 'scale0' (default), 'scale1', 'scale2' or 'scale3' - price accuracy, according to the selected accuracy step size to return the cumulative depth
         * @return {array} A dictionary of ~@link https://docs.ccxt.com/#/?id=order-book-structure order book structures~ indexed by $market symbols
         */
        $this->load_markets();
        $methodName = 'fetchOrderBook';
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $precision = null;
        list($precision, $params) = $this->handle_option_and_params($params, $methodName, 'precision');
        if ($precision !== null) {
            $request['precision'] = $precision;
        }
        $response = null;
        if ($market['spot']) {
            $response = $this->publicGetApiSpotV1MarketMergeDepth ($this->extend($request, $params));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1725137170814,
            //         "data" => {
            //             "asks" => array( array( 2507.07, 0.4248 ) ),
            //             "bids" => array( array( 2507.05, 0.1198 ) ),
            //             "ts" => "1725137170850",
            //             "scale" => "0.01",
            //             "precision" => "scale0",
            //             "isMaxPrecision" => "NO"
            //         }
            //     }
            //
        } elseif ($market['swap']) {
            $response = $this->publicGetApiMixV1MarketMergeDepth ($this->extend($request, $params));
        } else {
            throw new NotSupported($this->id . ' ' . $methodName . '() is not supported for ' . $market['type'] . ' type of markets');
        }
        $data = $this->safe_dict($response, 'data', array());
        $timestamp = $this->safe_integer($data, 'ts');
        return $this->parse_order_book($data, $symbol, $timestamp, 'bids', 'asks');
    }

    public function fetch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         *
         * @see https://coincatch.github.io/github.io/en/spot/#get-candle-$data
         * @see https://coincatch.github.io/github.io/en/mix/#get-candle-$data
         *
         * 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 (default 100)
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] timestamp in ms of the latest candle to fetch
         * @param {string} [$params->price] "mark" for mark price candles
         * @return {int[][]} A list of candles ordered, open, high, low, close, volume
         */
        $methodName = 'fetchOHLCV';
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $until = null;
        list($until, $params) = $this->handle_option_and_params($params, $methodName, 'until');
        $marketType = $market['type'];
        $timeframes = $this->options['timeframes'][$marketType];
        $encodedTimeframe = $this->safe_string($timeframes, $timeframe, $timeframe);
        $maxLimit = 1000;
        $requestedLimit = $limit;
        if (($since !== null) || ($until !== null)) {
            $requestedLimit = $maxLimit; // the exchange returns only last $limit candles, so we have to fetch max $limit if $since or $until are provided
        }
        if ($requestedLimit !== null) {
            $request['limit'] = $requestedLimit;
        }
        $response = null;
        if ($market['spot']) {
            $request['period'] = $encodedTimeframe;
            if ($since !== null) {
                $request['after'] = $since;
            }
            if ($until !== null) {
                $request['before'] = $until;
            }
            $response = $this->publicGetApiSpotV1MarketCandles ($this->extend($request, $params));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1725142465742,
            //         "data" => array(
            //             array(
            //                 "open" => "2518.6",
            //                 "high" => "2519.19",
            //                 "low" => "2518.42",
            //                 "close" => "2518.86",
            //                 "quoteVol" => "17193.239401",
            //                 "baseVol" => "6.8259",
            //                 "usdtVol" => "17193.239401",
            //                 "ts" => "1725142200000"
            //             ),
            //             ...
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_ohlcvs($data, $market, $timeframe, $since, $limit);
        } elseif ($market['swap']) {
            $request['granularity'] = $encodedTimeframe;
            if ($until === null) {
                $until = $this->milliseconds();
            }
            if ($since === null) {
                $duration = $this->parse_timeframe($timeframe);
                $since = $until - ($duration * $maxLimit * 1000);
            }
            $request['startTime'] = $since; // $since and $until are mandatory for swap
            $request['endTime'] = $until;
            $priceType = null;
            list($priceType, $params) = $this->handle_option_and_params($params, $methodName, 'price');
            if ($priceType === 'mark') {
                $request['kLineType'] = 'market mark index';
            }
            $response = $this->publicGetApiMixV1MarketCandles ($this->extend($request, $params));
            //
            //     array(
            //         array(
            //             "1725379020000",
            //             "57614",
            //             "57636",
            //             "57614",
            //             "57633",
            //             "28.725",
            //             "1655346.493"
            //         ),
            //         ...
            //     )
            //
            return $this->parse_ohlcvs($response, $market, $timeframe, $since, $limit);
        } else {
            throw new NotSupported($this->id . ' ' . $methodName . '() is not supported for ' . $market['type'] . ' type of markets');
        }
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        return array(
            $this->safe_integer_2($ohlcv, 'ts', 0),
            $this->safe_number_2($ohlcv, 'open', 1),
            $this->safe_number_2($ohlcv, 'high', 2),
            $this->safe_number_2($ohlcv, 'low', 3),
            $this->safe_number_2($ohlcv, 'close', 4),
            $this->safe_number_2($ohlcv, 'baseVol', 5),
        );
    }

    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://coincatch.github.io/github.io/en/spot/#get-recent-trades
         * @see https://coincatch.github.io/github.io/en/mix/#get-fills
         *
         * @param {string} $symbol unified $symbol of the $market to fetch trades for
         * @param {int} [$since] timestamp in ms of the earliest trade to fetch
         * @param {int} [$limit] the maximum amount of trades to fetch
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] timestamp in ms of the latest entry to fetch
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-trades trade structures~
         */
        $methodName = 'fetchTrades';
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $until = null;
        list($until, $params) = $this->handle_option_and_params($params, $methodName, 'until');
        $maxLimit = 1000;
        $requestLimit = $limit;
        if (($since !== null) || ($until !== null)) {
            $requestLimit = $maxLimit;
            if ($since !== null) {
                $request['startTime'] = $since;
            }
            if ($until !== null) {
                $request['endTime'] = $until;
            }
        }
        if ($requestLimit !== null) {
            $request['limit'] = $requestLimit;
        }
        $response = null;
        if ($market['spot']) {
            $response = $this->publicGetApiSpotV1MarketFillsHistory ($this->extend($request, $params));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1725198410976,
            //         "data" => array(
            //             {
            //                 "symbol" => "ETHUSDT_SPBL",
            //                 "tradeId" => "1214135619719827457",
            //                 "side" => "buy",
            //                 "fillPrice" => "2458.62",
            //                 "fillQuantity" => "0.4756",
            //                 "fillTime" => "1725198409967"
            //             }
            //         )
            //     }
            //
        } elseif ($market['swap']) {
            $response = $this->publicGetApiMixV1MarketFillsHistory ($this->extend($request, $params));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1725389251975,
            //         "data" => array(
            //             array(
            //                 "tradeId" => "1214936067582234782",
            //                 "price" => "57998.5",
            //                 "size" => "1.918",
            //                 "side" => "Sell",
            //                 "timestamp" => "1725389251000",
            //                 "symbol" => "BTCUSDT_UMCBL"
            //             ),
            //             ...
            //         )
            //     }
            //
        } else {
            throw new NotSupported($this->id . ' ' . $methodName . '() is not supported for ' . $market['type'] . ' type of markets');
        }
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_trades($data, $market, $since, $limit);
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // fetchTrades spot
        //     {
        //         "symbol" => "ETHUSDT_SPBL",
        //         "tradeId" => "1214135619719827457",
        //         "side" => "Buy",
        //         "fillPrice" => "2458.62",
        //         "fillQuantity" => "0.4756",
        //         "fillTime" => "1725198409967"
        //     }
        //
        // fetchTrades swap
        //     {
        //         "tradeId" => "1214936067582234782",
        //         "price" => "57998.5",
        //         "size" => "1.918",
        //         "side" => "Sell",
        //         "timestamp" => "1725389251000",
        //         "symbol" => "BTCUSDT_UMCBL"
        //     }
        //
        // fetchMyTrades spot
        //     {
        //         "accountId" => "1002820815393",
        //         "symbol" => "ETHUSDT_SPBL",
        //         "orderId" => "1217143186968068096",
        //         "fillId" => "1217143193356505089",
        //         "orderType" => "market",
        //         "side" => "buy",
        //         "fillPrice" => "2340.55",
        //         "fillQuantity" => "0.0042",
        //         "fillTotalAmount" => "9.83031",
        //         "feeCcy" => "ETH",
        //         "fees" => "-0.0000042",
        //         "takerMakerFlag" => "taker",
        //         "cTime" => "1725915471400"
        //     }
        //
        // fetchMyTrades swap
        //     {
        //         "tradeId" => "1225467075440189441",
        //         "symbol" => "ETHUSD_DMCBL",
        //         "orderId" => "1225467075288719360",
        //         "price" => "2362.03",
        //         "sizeQty" => "0.1",
        //         "fee" => "-0.00005996",
        //         "side" => "burst_close_long",
        //         "fillAmount" => "236.203",
        //         "profit" => "-0.0083359",
        //         "enterPointSource" => "SYS",
        //         "tradeSide" => "burst_close_long",
        //         "holdMode" => "double_hold",
        //         "takerMakerFlag" => "taker",
        //         "cTime" => "1727900039539"
        //     }
        //
        $marketId = $this->safe_string($trade, 'symbol');
        $market = $this->safe_market_custom($marketId, $market);
        $timestamp = $this->safe_integer_n($trade, array( 'fillTime', 'timestamp', 'cTime' ));
        $fees = $this->safe_string_2($trade, 'fees', 'fee');
        $feeCost = null;
        if ($fees !== null) {
            $feeCost = Precise::string_abs($fees);
        }
        $feeCurrency = $this->safe_string($trade, 'feeCcy');
        if (($feeCurrency === null) && ($market['settle'] !== null)) {
            $feeCurrency = $market['settle'];
        }
        $side = $this->safe_string_lower_2($trade, 'tradeSide', 'side');
        return $this->safe_trade(array(
            'id' => $this->safe_string_2($trade, 'tradeId', 'fillId'),
            'order' => $this->safe_string($trade, 'orderId'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $market['symbol'],
            'type' => $this->safe_string($trade, 'orderType'),
            'side' => $this->parse_order_side($side),
            'takerOrMaker' => $this->safe_string($trade, 'takerMakerFlag'),
            'price' => $this->safe_string_2($trade, 'fillPrice', 'price'),
            'amount' => $this->safe_string_n($trade, array( 'fillQuantity', 'size', 'sizeQty' )),
            'cost' => $this->safe_string_2($trade, 'fillTotalAmount', 'fillAmount'),
            'fee' => array(
                'cost' => $feeCost,
                'currency' => $feeCurrency,
            ),
            'info' => $trade,
        ), $market);
    }

    public function fetch_funding_rate(string $symbol, $params = array ()): array {
        /**
         * fetch the current funding rate
         *
         * @see https://coincatch.github.io/github.io/en/mix/#get-current-funding-rate
         *
         * @param {string} $symbol unified $market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=funding-rate-structure funding rate structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $marketId = $market['id'];
        $parts = explode('_', $marketId);
        $request = array(
            'symbol' => $marketId,
            'productType' => $this->safe_string($parts, 1),
        );
        $response = $this->publicGetApiMixV1MarketCurrentFundRate ($this->extend($request, $params));
        //
        //     {
        //         "code" => "00000",
        //         "msg" => "success",
        //         "requestTime" => 1725402130395,
        //         "data" => {
        //             "symbol" => "BTCUSDT_UMCBL",
        //             "fundingRate" => "0.000043"
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        return $this->parse_funding_rate($data, $market);
    }

    public function parse_funding_rate($contract, ?array $market = null) {
        $marketId = $this->safe_string($contract, 'symbol');
        $market = $this->safe_market_custom($marketId, $market);
        $fundingRate = $this->safe_number($contract, 'fundingRate');
        return array(
            'info' => $contract,
            'symbol' => $market['symbol'],
            'markPrice' => null,
            'indexPrice' => null,
            'interestRate' => null,
            'estimatedSettlePrice' => null,
            'timestamp' => null,
            'datetime' => null,
            'fundingRate' => $fundingRate,
            'fundingTimestamp' => null,
            'fundingDatetime' => null,
            'nextFundingRate' => null,
            'nextFundingTimestamp' => null,
            'nextFundingDatetime' => null,
            'previousFundingRate' => null,
            'previousFundingTimestamp' => null,
            'previousFundingDatetime' => null,
        );
    }

    public function handle_option_params_and_request(array $params, string $methodName, string $optionName, array $request, string $requestProperty, $defaultValue = null) {
        list($option, $paramsOmited) = $this->handle_option_and_params($params, $methodName, $optionName, $defaultValue);
        if ($option !== null) {
            $request[$requestProperty] = $option;
        }
        return array( $request, $paramsOmited );
    }

    public function fetch_funding_rate_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetches historical funding rate prices
         *
         * @see https://coincatch.github.io/github.io/en/mix/#get-history-funding-rate
         *
         * @param {string} $symbol unified $symbol of the $market to fetch the funding rate history for
         * @param {int} [$since] $timestamp in ms of the earliest funding rate to fetch
         * @param {int} [$limit] the maximum amount of entries to fetch
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->pageNo] the page number to fetch
         * @param {bool} [$params->nextPage] whether to query the next page (default false)
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=funding-rate-history-structure funding rate structures~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchFundingRateHistory() requires a $symbol argument');
        }
        $this->load_markets();
        $maxEntriesPerRequest = 100;
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $requestedLimit = $limit;
        if ($since !== null) {
            $requestedLimit = $maxEntriesPerRequest;
        }
        if ($requestedLimit !== null) {
            $request['pageSize'] = $requestedLimit;
        }
        $response = $this->publicGetApiMixV1MarketHistoryFundRate ($this->extend($request, $params));
        //
        //     {
        //         "code" => "00000",
        //         "msg" => "success",
        //         "requestTime" => 1725455810888,
        //         "data" => array(
        //             {
        //                 "symbol" => "BTCUSD",
        //                 "fundingRate" => "0.000635",
        //                 "settleTime" => "1724889600000"
        //             }
        //         )
        //     }
        //
        $data = $this->safe_list($response, 'data', array());
        $rates = array();
        for ($i = 0; $i < count($data); $i++) {
            $entry = $data[$i];
            $timestamp = $this->safe_integer($entry, 'settleTime');
            $rates[] = array(
                'info' => $entry,
                'symbol' => $this->safe_symbol($this->safe_string($entry, 'symbol'), $market, null, 'swap'),
                'fundingRate' => $this->safe_number($entry, 'fundingRate'),
                'timestamp' => $timestamp,
                'datetime' => $this->iso8601($timestamp),
            );
        }
        $sorted = $this->sort_by($rates, 'timestamp');
        return $this->filter_by_since_limit($sorted, $since, $limit);
    }

    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://coincatch.github.io/github.io/en/spot/#get-account-assets
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->type] 'spot' or 'swap' - the type of the market to fetch balance for (default 'spot')
         * @param {string} [$params->productType] *swap only* 'umcbl' or 'dmcbl' (default 'umcbl')
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
         */
        $this->load_markets();
        $methodName = 'fetchBalance';
        $marketType = null;
        list($marketType, $params) = $this->handle_market_type_and_params($methodName, null, $params);
        $response = null;
        if ($marketType === 'spot') {
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1725202685986,
            //         "data" => array(
            //             {
            //                 "coinId" => 2,
            //                 "coinName" => "USDT",
            //                 "available" => "99.20000000",
            //                 "frozen" => "0.00000000",
            //                 "lock" => "0.00000000",
            //                 "uTime" => "1724938746000"
            //             }
            //         )
            //     }
            //
            $response = $this->privateGetApiSpotV1AccountAssets ($params);
        } elseif ($marketType === 'swap') {
            $productType = 'umcbl';
            list($productType, $params) = $this->handle_option_and_params($params, $methodName, 'productType', $productType);
            $request = array(
                'productType' => $productType,
            );
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1726666298135,
            //         "data" => array(
            //             {
            //                 "marginCoin" => "USDT",
            //                 "locked" => "0",
            //                 "available" => "60",
            //                 "crossMaxAvailable" => "60",
            //                 "fixedMaxAvailable" => "60",
            //                 "maxTransferOut" => "60",
            //                 "equity" => "60",
            //                 "usdtEquity" => "60",
            //                 "btcEquity" => "0.001002360626",
            //                 "crossRiskRate" => "0",
            //                 "unrealizedPL" => "0",
            //                 "bonus" => "0",
            //                 "crossedUnrealizedPL" => null,
            //                 "isolatedUnrealizedPL" => null
            //             }
            //         )
            //     }
            //
            $response = $this->privateGetApiMixV1AccountAccounts ($this->extend($request, $params));
        } else {
            throw new NotSupported($this->id . ' ' . $methodName . '() is not supported for ' . $marketType . ' type of markets');
        }
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_balance($data);
    }

    public function parse_balance($balances): array {
        //
        // spot
        //     array(
        //         {
        //             "coinId" => 2,
        //             "coinName" => "USDT",
        //             "available" => "99.20000000",
        //             "frozen" => "0.00000000",
        //             "lock" => "0.00000000",
        //             "uTime" => "1724938746000"
        //         }
        //     )
        //
        // swap
        //     array(
        //         {
        //             "marginCoin" => "USDT",
        //             "locked" => "0",
        //             "available" => "60",
        //             "crossMaxAvailable" => "60",
        //             "fixedMaxAvailable" => "60",
        //             "maxTransferOut" => "60",
        //             "equity" => "60",
        //             "usdtEquity" => "60",
        //             "btcEquity" => "0.001002360626",
        //             "crossRiskRate" => "0",
        //             "unrealizedPL" => "0",
        //             "bonus" => "0",
        //             "crossedUnrealizedPL" => null,
        //             "isolatedUnrealizedPL" => null
        //         }
        //     )
        //
        $result = array(
            'info' => $balances,
        );
        for ($i = 0; $i < count($balances); $i++) {
            $balanceEntry = $this->safe_dict($balances, $i, array());
            $currencyId = $this->safe_string_2($balanceEntry, 'coinName', 'marginCoin');
            $code = $this->safe_currency_code($currencyId);
            $account = $this->account();
            $account['free'] = $this->safe_string($balanceEntry, 'available');
            $locked = $this->safe_string_2($balanceEntry, 'lock', 'locked');
            $frozen = $this->safe_string($balanceEntry, 'frozen', '0');
            $account['used'] = Precise::string_add($locked, $frozen);
            $account['total'] = $this->safe_string($balanceEntry, 'equity');
            $result[$code] = $account;
        }
        return $this->safe_balance($result);
    }

    public function transfer(string $code, float $amount, string $fromAccount, string $toAccount, $params = array ()): array {
        /**
         * transfer $currency internally between wallets on the same account
         *
         * @see https://coincatch.github.io/github.io/en/spot/#transfer
         *
         * @param {string} $code unified $currency $code
         * @param {float} $amount amount to transfer
         * @param {string} $fromAccount 'spot' or 'swap' or 'mix_usdt' or 'mix_usd' - account to transfer from
         * @param {string} $toAccount 'spot' or 'swap' or 'mix_usdt' or 'mix_usd' - account to transfer to
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->clientOrderId] a unique id for the transfer
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=transfer-structure transfer structure~
         */
        $this->load_markets();
        $currency = $this->currency($code);
        if ($fromAccount === 'swap') {
            if ($code === 'USDT') {
                $fromAccount = 'mix_usdt';
            } else {
                $fromAccount = 'mix_usd';
            }
        }
        if ($toAccount === 'swap') {
            if ($code === 'USDT') {
                $toAccount = 'mix_usdt';
            } else {
                $toAccount = 'mix_usd';
            }
        }
        $request = array(
            'coin' => $currency['id'],
            'amount' => $this->currency_to_precision($code, $amount),
            'fromType' => $fromAccount,
            'toType' => $toAccount,
        );
        $clientOrderId = null;
        list($clientOrderId, $params) = $this->handle_option_and_params($params, 'transfer', 'clientOrderId');
        if ($clientOrderId !== null) {
            $request['clientOid'] = $clientOrderId;
        }
        $response = $this->privatePostApiSpotV1WalletTransferV2 ($this->extend($request, $params));
        //
        //     {
        //         "code" => "00000",
        //         "msg" => "success",
        //         "requestTime" => 1726664727436,
        //         "data" => {
        //             "transferId" => "1220285801129066496",
        //             "clientOrderId" => null
        //         }
        //     }
        //
        return $this->parse_transfer($response, $currency);
    }

    public function parse_transfer($transfer, ?array $currency = null) {
        $msg = $this->safe_string($transfer, 'msg');
        $status = null;
        if ($msg === 'success') {
            $status = 'ok';
        }
        $data = $this->safe_dict($transfer, 'data', array());
        return array(
            'id' => $this->safe_string($data, 'transferId'),
            'timestamp' => null,
            'datetime' => null,
            'currency' => $this->safe_currency_code(null, $currency),
            'amount' => null,
            'fromAccount' => null,
            'toAccount' => null,
            'status' => $status,
            'info' => $transfer,
        );
    }

    public function fetch_deposit_address(string $code, $params = array ()): array {
        /**
         * fetch the deposit address for a $currency associated with this account
         *
         * @see https://coincatch.github.io/github.io/en/spot/#get-coin-address
         *
         * @param {string} $code unified $currency $code
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->network] network for fetch deposit address
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=address-structure address structure~
         */
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'coin' => $currency['id'],
        );
        $networkCode = null;
        list($networkCode, $params) = $this->handle_network_code_and_params($params);
        if ($networkCode === null) {
            $networkCode = $this->default_network_code($code);
        }
        if ($networkCode === null) {
            throw new ArgumentsRequired($this->id . ' fetchDepositAddress() requires a network parameter or a default network code');
        }
        $request['chain'] = $this->network_code_to_id($networkCode, $code);
        $response = $this->privateGetApiSpotV1WalletDepositAddress ($this->extend($request, $params));
        //
        //     {
        //         "code" => "00000",
        //         "msg" => "success",
        //         "requestTime" => 1725210515143,
        //         "data" => {
        //             "coin" => "USDT",
        //             "address" => "TKTUt7qiTaMgnTwZXjE3ZBkPB6LKhLPJyZ",
        //             "chain" => "TRC20",
        //             "tag" => null,
        //             "url" => "https://tronscan.org/#/transaction/"
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        $depositAddress = $this->parse_deposit_address($data, $currency);
        return $depositAddress;
    }

    public function parse_deposit_address($depositAddress, ?array $currency = null): array {
        //
        //     {
        //         "coin" => "USDT",
        //         "address" => "TKTUt7qiTaMgnTwZXjE3ZBkPB6LKhLPJyZ",
        //         "chain" => "TRC20",
        //         "tag" => null,
        //         "url" => "https://tronscan.org/#/transaction/"
        //     }
        //
        $address = $this->safe_string($depositAddress, 'address');
        $this->check_address($address);
        $networkId = $this->safe_string($depositAddress, 'chain');
        $network = $this->safe_string($this->options['networksById'], $networkId, $networkId);
        $tag = $this->safe_string($depositAddress, 'tag');
        return array(
            'currency' => $currency['code'],
            'address' => $address,
            'tag' => $tag,
            'network' => $network,
            'info' => $depositAddress,
        );
    }

    public function fetch_deposits(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all deposits made to an account
         *
         * @see https://coincatch.github.io/github.io/en/spot/#get-deposit-list
         *
         * @param {string} $code unified $currency $code of the $currency transferred
         * @param {int} [$since] the earliest time in ms to fetch transfers for (default 24 hours ago)
         * @param {int} [$limit] the maximum number of transfer structures to retrieve (not used by exchange)
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] the latest time in ms to fetch transfers for (default time now)
         * @param {int} [$params->pageNo] pageNo default 1
         * @param {int} [$params->pageSize] pageSize (default 20, max 100)
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transfer-structure transfer structures~
         */
        $methodName = 'fetchDeposits';
        $this->load_markets();
        $request = array();
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['coin'] = $currency['id'];
        }
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        $until = null;
        list($until, $params) = $this->handle_option_and_params($params, $methodName, 'until');
        if ($until !== null) {
            $request['endTime'] = $until;
        }
        $response = $this->privateGetApiSpotV1WalletDepositList ($this->extend($request, $params));
        //
        //     {
        //         "code" => "00000",
        //         "msg" => "success",
        //         "requestTime" => 1725205525239,
        //         "data" => array(
        //             {
        //                 "id" => "1213046466852196352",
        //                 "txId" => "824246b030cd84d56400661303547f43a1d9fef66cf968628dd5112f362053ff",
        //                 "coin" => "USDT",
        //                 "type" => "deposit",
        //                 "amount" => "99.20000000",
        //                 "status" => "success",
        //                 "toAddress" => "TKTUt7qiTaMgnTwZXjE3ZBkPB6LKhLPJyZ",
        //                 "fee" => null,
        //                 "chain" => "TRX(TRC20)",
        //                 "confirm" => null,
        //                 "clientOid" => null,
        //                 "tag" => null,
        //                 "fromAddress" => null,
        //                 "dest" => "on_chain",
        //                 "cTime" => "1724938735688",
        //                 "uTime" => "1724938746015"
        //             }
        //         )
        //     }
        //
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_transactions($data, $currency, $since, $limit);
    }

    public function fetch_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all withdrawals made from an account
         *
         * @see https://coincatch.github.io/github.io/en/spot/#get-withdraw-list-v2
         *
         * @param {string} $code unified $currency $code of the $currency transferred
         * @param {int} [$since] the earliest time in ms to fetch transfers for (default 24 hours ago)
         * @param {int} [$limit] the maximum number of transfer structures to retrieve (default 50, max 200)
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] the latest time in ms to fetch transfers for (default time now)
         * @param {string} [$params->clientOid] clientOid
         * @param {string} [$params->orderId] The $response orderId
         * @param {string} [$params->idLessThan] Requests the content on the page before this ID (older $data), the value input should be the orderId of the corresponding interface.
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $methodName = 'fetchWithdrawals';
        $this->load_markets();
        $request = array();
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['coin'] = $currency['id'];
        }
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $until = null;
        list($until, $params) = $this->handle_option_and_params($params, $methodName, 'until');
        if ($until !== null) {
            $request['endTime'] = $until;
        }
        $response = $this->privateGetApiSpotV1WalletWithdrawalListV2 ($this->extend($request, $params));
        // todo add after withdrawal
        //
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_transactions($data, $currency, $since, $limit);
    }

    public function withdraw(string $code, float $amount, string $address, ?string $tag = null, $params = array ()): array {
        /**
         * make a withdrawal
         *
         * @see https://coincatch.github.io/github.io/en/spot/#withdraw
         *
         * @param {string} $code unified $currency $code
         * @param {float} $amount the $amount to withdraw
         * @param {string} $address the $address to withdraw to
         * @param {string} [$tag]
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} $params->network network for withdraw (mandatory)
         * @param {string} [$params->remark] remark
         * @param {string} [$params->clientOid] custom id
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structure~
         */
        list($tag, $params) = $this->handle_withdraw_tag_and_params($tag, $params);
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'coin' => $currency['id'],
            'address' => $address,
            'amount' => $amount,
        );
        if ($tag !== null) {
            $request['tag'] = $tag;
        }
        $networkCode = null;
        list($networkCode, $params) = $this->handle_network_code_and_params($params);
        if ($networkCode !== null) {
            $request['chain'] = $this->network_code_to_id($networkCode);
        }
        $response = $this->privatePostApiSpotV1WalletWithdrawalV2 ($this->extend($request, $params));
        //
        //     {
        //         "code" => "00000",
        //         "msg" => "success",
        //         "data" => {
        //             "orderId":888291686266343424",
        //             "clientOrderId":"123"
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        return $this->parse_transaction($data, $currency);
    }

    public function parse_transaction($transaction, ?array $currency = null): array {
        //
        // fetchDeposits
        //
        //     {
        //         "id" => "1213046466852196352",
        //         "txId" => "824246b030cd84d56400661303547f43a1d9fef66cf968628dd5112f362053ff",
        //         "coin" => "USDT",
        //         "type" => "deposit",
        //         "amount" => "99.20000000",
        //         "status" => "success",
        //         "toAddress" => "TKTUt7qiTaMgnTwZXjE3ZBkPB6LKhLPJyZ",
        //         "fee" => null,
        //         "chain" => "TRX(TRC20)",
        //         "confirm" => null,
        //         "clientOid" => null,
        //         "tag" => null,
        //         "fromAddress" => null,
        //         "dest" => "on_chain",
        //         "cTime" => "1724938735688",
        //         "uTime" => "1724938746015"
        //     }
        //
        // withdraw
        //
        //     {
        //         "code" => "00000",
        //         "msg" => "success",
        //         "data" => {
        //             "orderId":888291686266343424",
        //             "clientOrderId":"123"
        //         }
        //     }
        //
        $status = $this->safe_string($transaction, 'status');
        if ($status === 'success') {
            $status = 'ok';
        }
        $txid = $this->safe_string($transaction, 'txId');
        $coin = $this->safe_string($transaction, 'coin');
        $code = $this->safe_currency_code($coin, $currency);
        $timestamp = $this->safe_integer($transaction, 'cTime');
        $amount = $this->safe_number($transaction, 'amount');
        $networkId = $this->safe_string($transaction, 'chain');
        $network = $this->safe_string($this->options['networksById'], $networkId, $networkId);
        $addressTo = $this->safe_string($transaction, 'toAddress');
        $addressFrom = $this->safe_string($transaction, 'fromAddress');
        $tag = $this->safe_string($transaction, 'tag');
        $type = $this->safe_string($transaction, 'type');
        $feeCost = $this->safe_number($transaction, 'fee');
        $fee = null;
        if ($feeCost !== null) {
            $fee = array(
                'cost' => $feeCost,
                'currency' => $code,
            );
        }
        return array(
            'info' => $transaction,
            'id' => $this->safe_string_2($transaction, 'id', 'orderId'),
            'txid' => $txid,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'network' => $network,
            'address' => null,
            'addressTo' => $addressTo,
            'addressFrom' => $addressFrom,
            'tag' => $tag,
            'tagTo' => null,
            'tagFrom' => null,
            'type' => $type,
            'amount' => $amount,
            'currency' => $code,
            'status' => $status,
            'updated' => null,
            'internal' => null,
            'comment' => null,
            'fee' => $fee,
        );
    }

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

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()): array {
        /**
         * create a trade order
         *
         * @see https://coincatch.github.io/github.io/en/spot/#place-order
         * @see https://coincatch.github.io/github.io/en/spot/#place-plan-order
         * @see https://coincatch.github.io/github.io/en/mix/#place-order
         * @see https://coincatch.github.io/github.io/en/mix/#place-plan-order
         *
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market' or 'limit' or 'LIMIT_MAKER' for spot, 'market' or 'limit' or 'STOP' for swap
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much of you want to trade in units of the base currency
         * @param {float} [$price] the $price that the order is to be fulfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {bool} [$params->hedged] *swap markets only* must be set to true if position mode is hedged (default false)
         * @param {float} [$params->cost] *spot $market buy only* the quote quantity that can be used alternative for the $amount
         * @param {float} [$params->triggerPrice] the $price that the order is to be triggered
         * @param {bool} [$params->postOnly] if true, the order will only be posted to the order book and not executed immediately
         * @param {string} [$params->timeInForce] 'GTC', 'IOC', 'FOK' or 'PO'
         * @param {string} [$params->clientOrderId] a unique id for the order - is mandatory for swap
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        $this->load_markets();
        $params['methodName'] = $this->safe_string($params, 'methodName', 'createOrder');
        $market = $this->market($symbol);
        if ($market['spot']) {
            return $this->create_spot_order($symbol, $type, $side, $amount, $price, $params);
        } elseif ($market['swap']) {
            return $this->create_swap_order($symbol, $type, $side, $amount, $price, $params);
        } else {
            throw new NotSupported($this->id . ' createOrder() is not supported for ' . $market['type'] . ' $type of markets');
        }
    }

    public function create_spot_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()): array {
        /**
         * create a trade order on spot $market
         *
         * @see https://coincatch.github.io/github.io/en/spot/#place-order
         * @see https://coincatch.github.io/github.io/en/spot/#place-plan-order
         *
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market' or 'limit'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much of you want to trade in units of the base currency
         * @param {float} [$price] the $price that 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 {float} [$params->cost] *$market buy only* the quote quantity that can be used alternative for the $amount
         * @param {float} [$params->triggerPrice] the $price that the order is to be triggered at
         * @param {bool} [$params->postOnly] if true, the order will only be posted to the order book and not executed immediately
         * @param {string} [$params->timeInForce] 'GTC', 'IOC', 'FOK' or 'PO'
         * @param {string} [$params->clientOrderId] a unique id for the order (max length 40)
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        $this->load_markets();
        $params['methodName'] = $this->safe_string($params, 'methodName', 'createSpotOrder');
        $request = $this->create_spot_order_request($symbol, $type, $side, $amount, $price, $params);
        $isPlanOrer = $this->safe_string($request, 'triggerPrice') !== null;
        $response = null;
        if ($isPlanOrer) {
            $response = $this->privatePostApiSpotV1PlanPlacePlan ($request);
        } else {
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1725915469815,
            //         "data" => {
            //             "orderId" => "1217143186968068096",
            //             "clientOrderId" => "8fa3eb89-2377-4519-a199-35d5db9ed262"
            //         }
            //     }
            //
            $response = $this->privatePostApiSpotV1TradeOrders ($request);
        }
        $data = $this->safe_dict($response, 'data', array());
        $market = $this->market($symbol);
        return $this->parse_order($data, $market);
    }

    public function create_spot_order_request(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()): 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 of you want to trade in units of the base currency
         * @param {float} [$price] the $price that 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 {float} [$params->triggerPrice] the $price that the order is to be triggered at
         * @param {float} [$params->cost] *$market buy only* the quote quantity that can be used alternative for the $amount
         * @param {bool} [$params->postOnly] if true, the order will only be posted to the order book and not executed immediately
         * @param {string} [$params->timeInForce] 'GTC', 'IOC', 'FOK' or 'PO' (default 'GTC')
         * @param {string} [$params->clientOrderId] a unique id for the order (max length 40)
         * @return {array} $request to be sent to the exchange
         */
        $methodName = 'createSpotOrderRequest';
        // spot $market info has no presicion so we do not use it
        list($methodName, $params) = $this->handle_param_string($params, 'methodName', $methodName);
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            'side' => $side,
            'orderType' => $type,
        );
        $isMarketOrder = ($type === 'market');
        $timeInForceAndParams = $this->handle_time_in_force_and_post_only($methodName, $params, $isMarketOrder);
        $params = $timeInForceAndParams['params'];
        $timeInForce = $timeInForceAndParams['timeInForce'];
        $cost = null;
        list($cost, $params) = $this->handle_param_string($params, 'cost');
        $triggerPrice = null;
        list($triggerPrice, $params) = $this->handle_param_string($params, 'triggerPrice');
        $isMarketBuy = $isMarketOrder && ($side === 'buy');
        if ((!$isMarketBuy) && ($cost !== null)) {
            throw new NotSupported($this->id . ' ' . $methodName . ' supports $cost parameter for $market buy orders only');
        }
        if ($isMarketBuy) {
            $costAndParams = $this->handle_requires_price_and_cost($methodName, $params, $price, $amount, $cost);
            $cost = $costAndParams['cost'];
            $params = $costAndParams['params'];
        }
        if ($triggerPrice === null) {
            if ($type === 'limit') {
                $request['price'] = $price; // spot markets have no precision
            }
            $request['quantity'] = $isMarketBuy ? $cost : $this->number_to_string($amount); // spot markets have no precision
            $request['force'] = $timeInForce ? $timeInForce : 'normal'; // the exchange requres force but accepts any value
        } else {
            $request['triggerPrice'] = $triggerPrice; // spot markets have no precision
            if ($timeInForce !== null) {
                $request['timeInForceValue'] = $timeInForce;
            }
            $clientOrderId = null;
            list($clientOrderId, $params) = $this->handle_param_string($params, 'clientOrderId');
            if ($clientOrderId !== null) {
                $request['clientOid'] = $clientOrderId;
            }
            if ($type === 'limit') {
                $request['executePrice'] = $price; // spot markets have no precision
            }
            $triggerType = null;
            if ($isMarketOrder) {
                $triggerType = 'market_price';
            } else {
                $triggerType = 'fill_price';
            }
            $request['triggerType'] = $triggerType;
            // tood check placeType
            $request['size'] = $isMarketOrder ? $cost : $this->number_to_string($amount); // spot markets have no precision
        }
        return $this->extend($request, $params);
    }

    public function handle_requires_price_and_cost(string $methodName, array $params = array (), ?float $price = null, ?float $amount = null, ?string $cost = null, string $side = 'buy') {
        $optionName = 'createMarket' . $this->capitalize($side) . 'OrderRequiresPrice';
        $requiresPrice = true;
        list($requiresPrice, $params) = $this->handle_option_and_params($params, $methodName, $optionName, true);
        $amountString = null;
        if ($amount !== null) {
            $amountString = $this->number_to_string($amount);
        }
        $priceString = null;
        if ($price !== null) {
            $priceString = $this->number_to_string($price);
        }
        if ($requiresPrice) {
            if (($price === null) && ($cost === null)) {
                throw new InvalidOrder($this->id . ' ' . $methodName . '() requires the $price argument for market ' . $side . ' orders to calculate the total $cost to spend ($amount * $price), alternatively set the ' . $optionName . ' option or param to false and pass the $cost to spend in the $amount argument');
            } elseif ($cost === null) {
                $cost = Precise::string_mul($amountString, $priceString);
            }
        } else {
            $cost = $cost ? $cost : $amountString;
        }
        $result = array(
            'cost' => $cost,
            'params' => $params,
        );
        return $result;
    }

    public function handle_time_in_force_and_post_only(string $methodName, array $params = array (), Bool $isMarketOrder = false) {
        $timeInForce = null;
        list($timeInForce, $params) = $this->handle_option_and_params($params, $methodName, 'timeInForce');
        $postOnly = false;
        list($postOnly, $params) = $this->handle_post_only($isMarketOrder, $timeInForce === 'post_only', $params);
        if ($postOnly) {
            $timeInForce = 'PO';
        }
        $timeInForce = $this->encode_time_in_force($timeInForce);
        $result = array(
            'timeInForce' => $timeInForce,
            'params' => $params,
        );
        return $result;
    }

    public function create_swap_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()): array {
        /**
         * create a trade order on swap $market
         *
         * @see https://coincatch.github.io/github.io/en/mix/#place-order
         * @see https://coincatch.github.io/github.io/en/mix/#place-plan-order
         * @see https://coincatch.github.io/github.io/en/mix/#place-stop-order
         *
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market' or 'limit'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much of you want to trade in units of the base currency
         * @param {float} [$price] the $price that the order is to be fulfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {bool} [$params->hedged] must be set to true if position mode is hedged (default false)
         * @param {bool} [$params->postOnly] *non-trigger orders only* if true, the order will only be posted to the order book and not executed immediately
         * @param {bool} [$params->reduceOnly] true or false whether the order is reduce only
         * @param {string} [$params->timeInForce] *non-trigger orders only* 'GTC', 'FOK', 'IOC' or 'PO'
         * @param {string} [$params->clientOrderId] a unique id for the order
         * @param {float} [$params->triggerPrice] the $price that the order is to be triggered at
         * @param {float} [$params->stopLossPrice] The $price at which a stop loss order is triggered at
         * @param {float} [$params->takeProfitPrice] The $price at which a take profit order is triggered at
         * @param {array} [$params->takeProfit] *takeProfit object in $params* containing the triggerPrice at which the attached take profit order will be triggered (perpetual swap markets only)
         * @param {float} [$params->takeProfit.triggerPrice] take profit trigger $price
         * @param {array} [$params->stopLoss] *stopLoss object in $params* containing the triggerPrice at which the attached stop loss order will be triggered (perpetual swap markets only)
         * @param {float} [$params->stopLoss.triggerPrice] stop loss trigger $price
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        $params['methodName'] = $this->safe_string($params, 'methodName', 'createSwapOrder');
        $this->load_markets();
        $market = $this->market($symbol);
        $request = $this->create_swap_order_request($symbol, $type, $side, $amount, $price, $params);
        $endpointType = $this->safe_string($request, 'endpointType');
        $request = $this->omit($request, 'endpointType');
        $response = null;
        if ($endpointType === 'trigger') {
            $response = $this->privatePostApiMixV1PlanPlacePlan ($request);
        } elseif ($endpointType === 'tpsl') {
            $response = $this->privatePostApiMixV1PlanPlaceTPSL ($request);
        } else { // standard
            $response = $this->privatePostApiMixV1OrderPlaceOrder ($request);
        }
        //
        //     {
        //         "code" => "00000",
        //         "msg" => "success",
        //         "requestTime" => 1727977301979,
        //         "data":
        //         {
        //             "clientOid" => "1225791137701519360",
        //             "orderId" => "1225791137697325056"
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        return $this->parse_order($data, $market);
    }

    public function create_swap_order_request(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()): 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 of you want to trade in units of the base currency
         * @param {float} [$price] the $price that the order is to be fulfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {bool} [$params->hedged] must be set to true if position mode is $hedged (default false)
         * @param {bool} [$params->postOnly] *non-trigger orders only* if true, the order will only be posted to the order book and not executed immediately
         * @param {bool} [$params->reduceOnly] true or false whether the order is reduce only
         * @param {string} [$params->timeInForce] *non-trigger orders only* 'GTC', 'FOK', 'IOC' or 'PO'
         * @param {string} [$params->clientOrderId] a unique id for the order
         * @param {float} [$params->triggerPrice] the $price that the order is to be triggered at
         * @param {float} [$params->stopLossPrice] The $price at which a stop loss order is triggered at
         * @param {float} [$params->takeProfitPrice] The $price at which a take profit order is triggered at
         * @param {array} [$params->takeProfit] *takeProfit object in $params* containing the triggerPrice at which the attached take profit order will be triggered (perpetual swap markets only)
         * @param {float} [$params->takeProfit.triggerPrice] take profit trigger $price
         * @param {array} [$params->stopLoss] *stopLoss object in $params* containing the triggerPrice at which the attached stop loss order will be triggered (perpetual swap markets only)
         * @param {float} [$params->stopLoss.triggerPrice] stop loss trigger $price
         * @return {array} $request to be sent to the exchange
         */
        $methodName = 'createSwapOrderRequest';
        list($methodName, $params) = $this->handle_param_string($params, 'methodName', $methodName);
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            'marginCoin' => $market['settleId'],
            'size' => $this->amount_to_precision($symbol, $amount),
        );
        list($request, $params) = $this->handle_option_params_and_request($params, $methodName, 'clientOrderId', $request, 'clientOid');
        $isMarketOrder = ($type === 'market');
        $params = $this->handle_trigger_stop_loss_and_take_profit($symbol, $side, $type, $price, $methodName, $params);
        $endpointType = $this->safe_string($params, 'endpointType');
        if (($endpointType === null) || ($endpointType === 'standard')) {
            $timeInForceAndParams = $this->handle_time_in_force_and_post_only($methodName, $params, $isMarketOrder); // only for non-trigger orders
            $params = $timeInForceAndParams['params'];
            $timeInForce = $timeInForceAndParams['timeInForce'];
            if ($timeInForce !== null) {
                $request['timeInForceValue'] = $timeInForce;
            }
            if ($price !== null) {
                $request['price'] = $this->price_to_precision($symbol, $price);
            }
        }
        if (($endpointType !== 'tpsl')) {
            $request['orderType'] = $type;
            $sideIsExchangeSpecific = false;
            $hedged = false;
            if (($side === 'buy_single') || ($side === 'sell_single') || ($side === 'open_long') || ($side === 'open_short') || ($side === 'close_long') || ($side === 'close_short')) {
                $sideIsExchangeSpecific = true;
                if (($side !== 'buy_single') && ($side !== 'sell_single')) {
                    $hedged = true;
                }
            }
            if (!$sideIsExchangeSpecific) {
                list($hedged, $params) = $this->handle_option_and_params($params, $methodName, 'hedged', $hedged);
                // $hedged and non-$hedged orders have different $side values and $reduceOnly handling
                $reduceOnly = $this->safe_bool($params, 'reduceOnly');
                if ($hedged) {
                    if (($reduceOnly !== null) && $reduceOnly) {
                        if ($side === 'buy') {
                            $side = 'close_short';
                        } elseif ($side === 'sell') {
                            $side = 'close_long';
                        }
                    } else {
                        if ($side === 'buy') {
                            $side = 'open_long';
                        } elseif ($side === 'sell') {
                            $side = 'open_short';
                        }
                    }
                } else {
                    $side = strtolower($side) . '_single';
                }
            }
            if ($hedged) {
                $params = $this->omit($params, 'reduceOnly');
            }
            $request['side'] = $side;
        }
        return $this->extend($request, $params);
    }

    public function handle_trigger_stop_loss_and_take_profit($symbol, $side, $type, $price, $methodName = 'createOrder', $params = array ()) {
        $request = array();
        $endpointType = 'standard'; // standard, trigger, tpsl, trailing - to define the endpoint to use
        $stopLossPrice = $this->safe_string($params, 'stopLossPrice');
        $takeProfitPrice = $this->safe_string($params, 'takeProfitPrice');
        $requestTriggerPrice = null;
        $takeProfitParams = $this->safe_dict($params, 'takeProfit');
        $stopLossParams = $this->safe_dict($params, 'stopLoss');
        $triggerPrice = $this->safe_string_2($params, 'triggerPrice', 'stopPrice');
        $isTrigger = ($triggerPrice !== null);
        $trailingPercent = $this->safe_string($params, 'trailingPercent');
        $trailingTriggerPrice = $this->safe_string($params, 'trailingTriggerPrice');
        $hasTPPrice = ($takeProfitPrice !== null);
        $hasSLPrice = ($stopLossPrice !== null);
        $hasTPParams = ($takeProfitParams !== null);
        if ($hasTPParams && !$hasTPPrice) {
            $takeProfitPrice = $this->safe_string($takeProfitParams, 'triggerPrice');
            $hasTPPrice = ($takeProfitPrice !== null);
        }
        $hasSLParams = ($stopLossParams !== null);
        if ($hasSLParams && !$hasSLPrice) {
            $stopLossPrice = $this->safe_string($stopLossParams, 'triggerPrice');
            $hasSLPrice = ($stopLossPrice !== null);
        }
        $hasBothTPAndSL = $hasTPPrice && $hasSLPrice;
        $isTrailingPercentOrder = ($trailingPercent !== null);
        $isMarketOrder = ($type === 'market');
        // handle with $triggerPrice $stopLossPrice and $takeProfitPrice
        if ($hasBothTPAndSL || $isTrigger || ($methodName === 'createOrderWithTakeProfitAndStopLoss')) {
            if ($isTrigger) {
                if ($isMarketOrder) {
                    $request['triggerType'] = 'market_price';
                } else {
                    $request['triggerType'] = 'fill_price';
                    $request['executePrice'] = $this->price_to_precision($symbol, $price);
                }
                $request['triggerPrice'] = $this->price_to_precision($symbol, $triggerPrice);
                $endpointType = 'trigger'; // if order also has $triggerPrice we use endpoint for trigger orders
            }
            if ($methodName === 'createOrders') {
                $endpointType = null; // we do not provide $endpointType for createOrders
            }
            if ($hasTPPrice) {
                $request['presetTakeProfitPrice'] = $takeProfitPrice;
            }
            if ($hasSLPrice) {
                $request['presetStopLossPrice'] = $stopLossPrice;
            }
        } elseif ($hasTPPrice || $hasSLPrice || $isTrailingPercentOrder) {
            if (!$isMarketOrder) {
                throw new NotSupported($this->id . ' ' . $methodName . '() supports does not support ' . $type . ' $type of stop loss and take profit orders (only market $type is supported for stop loss and take profit orders). To create a market order with stop loss or take profit attached use createOrderWithTakeProfitAndStopLoss()');
            }
            $endpointType = 'tpsl'; // if order has only one of the two we use endpoint for tpsl orders
            $holdSide = 'long';
            if ($side === 'buy') {
                $holdSide = 'short';
            }
            $request['holdSide'] = $holdSide;
            if ($isTrailingPercentOrder) {
                if ($trailingTriggerPrice === null) {
                    throw new ArgumentsRequired($this->id . ' ' . $methodName . '() requires the $trailingTriggerPrice parameter for trailing stop orders');
                }
                $requestTriggerPrice = $trailingTriggerPrice;
                $request['rangeRate'] = $trailingPercent;
                $request['planType'] = 'moving_plan';
            } elseif ($hasTPPrice) { // take profit
                $requestTriggerPrice = $takeProfitPrice;
                $request['planType'] = 'profit_plan';
            } else { // stop loss
                $requestTriggerPrice = $stopLossPrice;
                $request['planType'] = 'loss_plan';
            }
            $request['triggerPrice'] = $this->price_to_precision($symbol, $requestTriggerPrice);
        }
        if ($endpointType !== null) {
            $request['endpointType'] = $endpointType;
        }
        $params = $this->omit($params, array( 'stopLoss', 'takeProfit', 'stopLossPrice', 'takeProfitPrice', 'triggerPrice', 'stopPrice', 'trailingPercent', 'trailingTriggerPrice' ));
        return $this->extend($request, $params);
    }

    public function create_order_with_take_profit_and_stop_loss(string $symbol, string $type, string $side, float $amount, ?float $price = null, ?float $takeProfit = null, ?float $stopLoss = null, $params = array ()): array {
        /**
         * *swap markets only* create an order with a stop loss or take profit attached ($type 3)
         * @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 or the number of contracts
         * @param {float} [$price] the $price to fulfill the order, in units of the quote currency, ignored in $market orders
         * @param {float} [$takeProfit] the take profit $price, in units of the quote currency
         * @param {float} [$stopLoss] the stop loss $price, in units of the quote currency
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        $methodName = 'createOrderWithTakeProfitAndStopLoss';
        $this->load_markets();
        $market = $this->market($symbol);
        if (!$market['swap']) {
            throw new NotSupported($this->id . ' ' . $methodName . '() is supported for swap markets only');
        }
        $params['methodName'] = $methodName;
        return parent::create_order_with_take_profit_and_stop_loss($symbol, $type, $side, $amount, $price, $takeProfit, $stopLoss, $params);
    }

    public function encode_time_in_force(?string $timeInForce): ?string {
        $timeInForceMap = array(
            'GTC' => 'normal',
            'IOC' => 'iok',
            'FOK' => 'fok',
            'PO' => 'post_only',
        );
        return $this->safe_string($timeInForceMap, $timeInForce, $timeInForce);
    }

    public function create_orders(array $orders, $params = array ()) {
        /**
         * create a list of trade $orders (all $orders should be of the same $symbol)
         *
         * @see https://coincatch.github.io/github.io/en/spot/#batch-order
         *
         * @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 (max 50 entries)
         * @param {array} [$params] extra parameters specific to the api endpoint
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        $this->load_markets();
        // same $symbol for all $orders
        $methodName = 'createOrders';
        $params['methodName'] = $methodName;
        $ordersRequests = array();
        $symbols = array();
        for ($i = 0; $i < count($orders); $i++) {
            $rawOrder = $orders[$i];
            $symbol = $this->safe_string($rawOrder, 'symbol');
            $symbols[] = $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());
            $orderRequest = $this->create_order_request($symbol, $type, $side, $amount, $price, $orderParams);
            $triggerPrice = $this->safe_string($orderParams, 'triggerPrice');
            if ($triggerPrice !== null) {
                throw new NotSupported($this->id . ' ' . $methodName . '() does not support trigger orders');
            }
            $clientOrderId = $this->safe_string($orderRequest, 'clientOrderId');
            if ($clientOrderId === null) {
                $orderRequest['clientOrderId'] = $this->uuid(); // both spot and swap endpoints require $clientOrderId
            }
            $ordersRequests[] = $orderRequest;
        }
        $symbols = $this->unique($symbols);
        $symbolsLength = count($symbols);
        if ($symbolsLength !== 1) {
            throw new BadRequest($this->id . ' createOrders() requires all $orders to be of the same symbol');
        }
        $ordersSymbol = $this->safe_string($symbols, 0);
        $market = $this->market($ordersSymbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $marketType = $market['type'];
        $response = null;
        $responseOrders = null;
        $propertyName = null;
        if ($marketType === 'spot') {
            $request['orderList'] = $ordersRequests;
            $response = $this->privatePostApiSpotV1TradeBatchOrders ($this->extend($request, $params));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1726160718706,
            //         "data" => {
            //             "resultList" => array(
            //                 {
            //                     "orderId" => "1218171835238367232",
            //                     "clientOrderId" => "28759338-ca10-42dd-8ac3-5183785ef60b"
            //                 }
            //             ),
            //             "failure" => array(
            //                 array(
            //                     "orderId" => "",
            //                     "clientOrderId" => "ee2e67c9-47fc-4311-9cc1-737ec408d509",
            //                     "errorMsg" => "The order $price of eth_usdt cannot be less than 5.00% of the current $price",
            //                     "errorCode" => "43008"
            //                 ),
            //                 {
            //                     "orderId" => "",
            //                     "clientOrderId" => "1af2defa-0c2d-4bb5-acb7-6feb6a86787a",
            //                     "errorMsg" => "less than the minimum $amount 1 USDT",
            //                     "errorCode" => "45110"
            //                 }
            //             )
            //         }
            //     }
            //
            $propertyName = 'resultList';
        } elseif ($market['swap']) {
            $request['marginCoin'] = $market['settleId'];
            $request['orderDataList'] = $ordersRequests;
            $response = $this->privatePostApiMixV1OrderBatchOrders ($this->extend($request, $params));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1729100084017,
            //         "data" => {
            //             "orderInfo" => array(
            //                 {
            //                     "orderId" => "1230500426827522049",
            //                     "clientOid" => "1230500426898825216"
            //                 }
            //             ),
            //             "failure" => array(
            //                 {
            //                     "orderId" => "",
            //                     "clientOid" => null,
            //                     "errorMsg" => "The order $price exceeds the maximum $price limit => 2,642.53",
            //                     "errorCode" => "22047"
            //                 }
            //             ),
            //             "result" => true
            //         }
            //     }
            //
            $propertyName = 'orderInfo';
        } else {
            throw new NotSupported($this->id . ' ' . $methodName . '() is not supported for ' . $marketType . ' $type of markets');
        }
        $data = $this->safe_dict($response, 'data', array());
        $responseOrders = $this->safe_list($data, $propertyName, array());
        return $this->parse_orders($responseOrders);
    }

    public function create_order_request(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()): array {
        $methodName = $this->safe_string($params, 'methodName', 'createOrderRequest');
        $params['methodName'] = $methodName;
        $market = $this->market($symbol);
        if ($market['spot']) {
            return $this->create_spot_order_request($symbol, $type, $side, $amount, $price, $params);
        } elseif ($market['swap']) {
            return $this->create_swap_order_request($symbol, $type, $side, $amount, $price, $params);
        } else {
            throw new NotSupported($this->id . ' ' . $methodName . '() is not supported for ' . $market['type'] . ' $type of markets');
        }
    }

    public function edit_order(string $id, string $symbol, string $type, string $side, ?float $amount = null, ?float $price = null, $params = array ()) {
        /**
         * edit a trade trigger, stop-looss or take-profit order
         *
         * @see https://coincatch.github.io/github.io/en/spot/#modify-plan-order
         *
         * @param {string} $id order $id
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market' or 'limit'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much of currency you want to trade in units of base currency
         * @param {float} [$price] the $price at which the order is to be fulfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $methodName = 'editOrder';
        // only trigger, stop-looss or take-profit orders can be edited
        $params['methodName'] = $methodName;
        $this->load_markets();
        $market = $this->market($symbol);
        if ($market['spot']) {
            return $this->edit_spot_order($id, $symbol, $type, $side, $amount, $price, $params);
        } else {
            // todo return $this->editSwapOrder ($id, $symbol, $type, $side, $amount, $price, $params);
            throw new NotSupported($this->id . ' ' . $methodName . '() is not supported for ' . $market['type'] . ' $type of markets');
        }
    }

    public function edit_spot_order(string $id, string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        /**
         * @ignore
         * edit a trade order
         *
         * @see https://coincatch.github.io/github.io/en/spot/#modify-plan-order
         *
         * @param {string} $id order $id
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market' or 'limit'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much of currency you want to trade in units of base currency
         * @param {float} [$price] the $price at which the order is to be fulfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->clientOrderId] a unique $id for the order that can be used alternative for the $id
         * @param {string} $params->triggerPrice *mandatory* the $price that the order is to be triggered at
         * @param {float} [$params->cost] *$market buy only* the quote quantity that can be used alternative for the $amount
         * @return {array} an ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $this->load_markets();
        $methodName = 'editSpotOrder';
        list($methodName, $params) = $this->handle_param_string($params, 'methodName', $methodName);
        $market = $this->market($symbol);
        if (!$market['spot']) {
            throw new NotSupported($this->id . ' editSpotOrder() does not support ' . $market['type'] . ' orders');
        }
        $request = array(
            'orderType' => $type,
        );
        $clientOrderId = $this->safe_string($params, 'clientOrderId');
        if ($clientOrderId !== null) {
            $request['clientOid'] = $clientOrderId;
        } elseif ($id === null) {
            throw new BadRequest($this->id . ' ' . $methodName . '() requires $id or clientOrderId');
        } else {
            $request['orderId'] = $id;
        }
        $cost = null;
        list($cost, $params) = $this->handle_param_string($params, 'cost');
        $isMarketBuy = ($type === 'market') && ($side === 'buy');
        if ((!$isMarketBuy) && ($cost !== null)) {
            throw new NotSupported($this->id . ' ' . $methodName . '() supports $cost parameter for $market buy orders only');
        }
        if ($amount !== null) {
            if ($isMarketBuy) {
                $costAndParams = $this->handle_requires_price_and_cost($methodName, $params, $price, $amount, $cost);
                $cost = $costAndParams['cost'];
                $params = $costAndParams['params'];
            } else {
                $request['size'] = $this->number_to_string($amount); // spot markets have no precision
            }
        }
        if ($cost !== null) {
            $request['size'] = $cost; // spot markets have no precision
        }
        if (($type === 'limit') && ($price !== null)) {
            $request['price'] = $price; // spot markets have no precision
        }
        $response = $this->privatePostApiSpotV1PlanModifyPlan ($this->extend($request, $params));
        //
        //     {
        //         "code" => "00000",
        //         "msg" => "success",
        //         "requestTime" => 1668136575920,
        //         "data" => {
        //             "orderId" => "974792060738441216",
        //             "clientOrderId" => "974792554995224576"
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        return $this->parse_order($data, $market);
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()): array {
        /**
         * fetches information on an $order made by the user (non-trigger orders only)
         *
         * @see https://coincatch.github.io/github.io/en/spot/#get-$order-details
         * @see https://coincatch.github.io/github.io/en/mix/#get-$order-details
         *
         * @param {string} $id the $order $id
         * @param {string} $symbol unified $symbol of the $market the $order was made in (is mandatory for swap)
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->type] 'spot' or 'swap' - the type of the $market to fetch entry for (default 'spot')
         * @param {string} [$params->clientOrderId] a unique $id for the $order that can be used alternative for the $id
         * @return {array} An ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structure~
         */
        $methodName = 'fetchOrder';
        // for non-trigger orders only
        $this->load_markets();
        $request = array();
        $clientOrderId = $this->safe_string($params, 'clientOrderId');
        if ($clientOrderId === null) {
            $request['orderId'] = $id;
        }
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        $marketType = 'spot';
        list($marketType, $params) = $this->handle_market_type_and_params($methodName, $market, $params, $marketType);
        $response = null;
        $order = null;
        if ($marketType === 'spot') {
            // user could query cancelled/filled $order details within 24 hours, After 24 hours should use fetchOrders
            $response = $this->privatePostApiSpotV1TradeOrderInfo ($this->extend($request, $params));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1725918004434,
            //         "data" => array(
            //             {
            //                 "accountId" => "1002820815393",
            //                 "symbol" => "ETHUSDT_SPBL",
            //                 "orderId" => "1217143186968068096",
            //                 "clientOrderId" => "8fa3eb89-2377-4519-a199-35d5db9ed262",
            //                 "price" => "0",
            //                 "quantity" => "10.0000000000000000",
            //                 "orderType" => "market",
            //                 "side" => "buy",
            //                 "status" => "full_fill",
            //                 "fillPrice" => "2340.5500000000000000",
            //                 "fillQuantity" => "0.0042000000000000",
            //                 "fillTotalAmount" => "9.8303100000000000",
            //                 "enterPointSource" => "API",
            //                 "feeDetail" => "{
            //                     \"ETH\" => array(
            //                         \"deduction\" => false,
            //                         \"feeCoinCode\" => \"ETH\",
            //                         \"totalDeductionFee\" => 0,
            //                         \"totalFee\" => -0.0000042000000000),
            //                         \"newFees\" => array(
            //                         \"c\" => 0,
            //                         \"d\" => 0,
            //                         \"deduction\" => false,
            //                         \"r\" => -0.0000042,
            //                         \"t\" => -0.0000042,
            //                         \"totalDeductionFee\" => 0
            //                     }
            //                 )",
            //                 "orderSource" => "market",
            //                 "cTime" => "1725915469877"
            //             }
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data');
            if ($data === null) {
                $response = json_decode($response, $as_associative_array = true); // the $response from closed orders is not a standard JSON
                $data = $this->safe_list($response, 'data', array());
            }
            $order = $this->safe_dict($data, 0, array());
        } elseif ($marketType === 'swap') {
            if ($market === null) {
                throw new ArgumentsRequired($this->id . ' ' . $methodName . '() requires a $symbol argument for ' . $marketType . ' type of markets');
            }
            $request['symbol'] = $market['id'];
            if ($clientOrderId !== null) {
                $params = $this->omit($params, 'clientOrderId');
                $request['clientOid'] = $clientOrderId;
            }
            $response = $this->privateGetApiMixV1OrderDetail ($this->extend($request, $params));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1727981421364,
            //         "data" => {
            //             "symbol" => "ETHUSDT_UMCBL",
            //             "size" => 0.01,
            //             "orderId" => "1225791137697325056",
            //             "clientOid" => "1225791137701519360",
            //             "filledQty" => 0.01,
            //             "fee" => -0.01398864,
            //             "price" => null,
            //             "priceAvg" => 2331.44,
            //             "state" => "filled",
            //             "side" => "close_long",
            //             "timeInForce" => "normal",
            //             "totalProfits" => -2.23680000,
            //             "posSide" => "long",
            //             "marginCoin" => "USDT",
            //             "filledAmount" => 23.3144,
            //             "orderType" => "market",
            //             "leverage" => "5",
            //             "marginMode" => "crossed",
            //             "reduceOnly" => true,
            //             "enterPointSource" => "API",
            //             "tradeSide" => "close_long",
            //             "holdMode" => "double_hold",
            //             "orderSource" => "market",
            //             "cTime" => "1727977302003",
            //             "uTime" => "1727977303604"
            //         }
            //     }
            //
            $order = $this->safe_dict($response, 'data', array());
        } else {
            throw new NotSupported($this->id . ' ' . $methodName . '() is not supported for ' . $marketType . ' type of markets');
        }
        return $this->parse_order($order, $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://coincatch.github.io/github.io/en/spot/#get-order-list
         * @see https://coincatch.github.io/github.io/en/spot/#get-current-plan-orders
         * @see https://coincatch.github.io/github.io/en/mix/#get-open-order
         * @see https://coincatch.github.io/github.io/en/mix/#get-all-open-order
         * @see https://coincatch.github.io/github.io/en/mix/#get-plan-order-tpsl-list
         *
         * @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 {boolean} [$params->trigger] true if fetching trigger orders (default false)
         * @param {string} [$params->type] 'spot' or 'swap' - the type of the $market to fetch entries for (default 'spot')
         * @param {string} [$params->productType] *swap only* 'umcbl' or 'dmcbl' - the product type of the $market to fetch entries for (default 'umcbl')
         * @param {string} [$params->marginCoin] *swap only* the margin coin of the $market to fetch entries for
         * @param {string} [$params->isPlan] *swap trigger only* 'plan' or 'profit_loss' ('plan' (default) for trigger (plan) orders, 'profit_loss' for stop-loss and take-profit orders)
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $methodName = 'fetchOpenOrders';
        $this->load_markets();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        $marketType = 'spot';
        list($marketType, $params) = $this->handle_market_type_and_params($methodName, $market, $params, $marketType);
        $params['methodName'] = $methodName;
        if ($marketType === 'spot') {
            return $this->fetch_open_spot_orders($symbol, $since, $limit, $params);
        } elseif ($marketType === 'swap') {
            return $this->fetch_open_swap_orders($symbol, $since, $limit, $params);
        } else {
            throw new NotSupported($this->id . ' ' . $methodName . '() is not supported for ' . $marketType . ' type of markets');
        }
    }

    public function fetch_open_spot_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * @ignore
         * fetch all unfilled currently open orders for spot markets
         *
         * @see https://coincatch.github.io/github.io/en/spot/#get-order-list
         * @see https://coincatch.github.io/github.io/en/spot/#get-current-plan-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 {boolean} [$params->trigger] true if fetching trigger orders (default false)
         * @param {string} [$params->lastEndId] *for trigger orders only* the last order id to fetch entries after
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        $methodName = 'fetchOpenSpotOrders';
        list($methodName, $params) = $this->handle_param_string($params, 'methodName', $methodName);
        $request = array();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        $isTrigger = false;
        list($isTrigger, $params) = $this->handle_option_and_params_2($params, $methodName, 'trigger', 'stop', $isTrigger);
        $result = null;
        if ($isTrigger) {
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' ' . $methodName . '() requires a $symbol argument for trigger orders');
            }
            if ($limit !== null) {
                $request['pageSize'] = $limit;
            }
            $response = $this->privatePostApiSpotV1PlanCurrentPlan ($this->extend($request, $params));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1728664710749,
            //         "data" => {
            //             "nextFlag" => false,
            //             "endId" => 1228661660806787072,
            //             "orderList" => array(
            //                 array(
            //                     "orderId" => "1228669617606991872",
            //                     "clientOid" => "1228669617573437440",
            //                     "symbol" => "ETHUSDT_SPBL",
            //                     "size" => "50",
            //                     "executePrice" => "0",
            //                     "triggerPrice" => "4000",
            //                     "status" => "not_trigger",
            //                     "orderType" => "market",
            //                     "side" => "sell",
            //                     "triggerType" => "fill_price",
            //                     "enterPointSource" => "API",
            //                     "placeType" => null,
            //                     "cTime" => "1728663585092",
            //                     "uTime" => null
            //                 ),
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $result = $this->safe_list($data, 'orderList', array());
        } else {
            $response = $this->privatePostApiSpotV1TradeOpenOrders ($this->extend($request, $params));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1725965783430,
            //         "data" => array(
            //             array(
            //                 "accountId" => "1002820815393",
            //                 "symbol" => "ETHUSDT_SPBL",
            //                 "orderId" => "1217347655911653376",
            //                 "clientOrderId" => "c57c07d1-bd00-4167-95e2-9b22a55fbc28",
            //                 "price" => "2000.0000000000000000",
            //                 "quantity" => "0.0010000000000000",
            //                 "orderType" => "limit",
            //                 "side" => "buy",
            //                 "status" => "new",
            //                 "fillPrice" => "0",
            //                 "fillQuantity" => "0.0000000000000000",
            //                 "fillTotalAmount" => "0.0000000000000000",
            //                 "enterPointSource" => "API",
            //                 "feeDetail" => "",
            //                 "orderSource" => "normal",
            //                 "cTime" => "1725964219072"
            //             ),
            //             ...
            //         )
            //     }
            //
            $result = $this->safe_list($response, 'data', array());
        }
        return $this->parse_orders($result, $market, $since, $limit);
    }

    public function fetch_open_swap_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * @ignore
         * fetch all unfilled currently open orders for swap markets
         *
         * @see https://coincatch.github.io/github.io/en/mix/#get-open-order
         * @see https://coincatch.github.io/github.io/en/mix/#get-all-open-order
         * @see https://coincatch.github.io/github.io/en/mix/#get-$plan-order-tpsl-list
         *
         * @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 {boolean} [$params->trigger] true if fetching trigger orders (default false)
         * @param {string} [$params->isPlan] 'plan' or 'profit_loss' ('plan' (default) for trigger ($plan) orders, 'profit_loss' for stop-loss and take-profit orders)
         * @param {string} [$params->productType] 'umcbl' or 'dmcbl' - the product type of the $market to fetch entries for (default 'umcbl')
         * @param {string} [$params->marginCoin] the margin coin of the $market to fetch entries for
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        $methodName = 'fetchOpenSwapOrders';
        list($methodName, $params) = $this->handle_param_string($params, 'methodName', $methodName);
        $isTrigger = false;
        list($isTrigger, $params) = $this->handle_option_and_params_2($params, $methodName, 'trigger', 'stop', $isTrigger);
        $plan = null;
        list($plan, $params) = $this->handle_option_and_params($params, $methodName, 'isPlan', $plan);
        $productType = $this->handle_option($methodName, 'productType');
        $market = null;
        $response = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
            );
            if (($isTrigger) || ($plan !== null)) { // the same endpoint is used for trigger and stop-loss/take-profit orders
                if ($productType !== null) {
                    $request['productType'] = $productType;
                }
                if ($plan !== null) {
                    $request['isPlan'] = $plan; // current param is used to define the type of the orders to fetch (trigger or stop-loss/take-profit)
                }
                //
                //     {
                //         "code" => "00000",
                //         "msg" => "success",
                //         "requestTime" => 1729168682690,
                //         "data" => array(
                //             {
                //                 "orderId" => "1230779428914049025",
                //                 "clientOid" => "1230779428914049024",
                //                 "symbol" => "ETHUSDT_UMCBL",
                //                 "marginCoin" => "USDT",
                //                 "size" => "0.01",
                //                 "executePrice" => "1000",
                //                 "triggerPrice" => "1200",
                //                 "status" => "not_trigger",
                //                 "orderType" => "limit",
                //                 "planType" => "normal_plan",
                //                 "side" => "buy_single",
                //                 "triggerType" => "fill_price",
                //                 "presetTakeProfitPrice" => "4000",
                //                 "presetTakeLossPrice" => "900",
                //                 "rangeRate" => "",
                //                 "enterPointSource" => "API",
                //                 "tradeSide" => "buy_single",
                //                 "holdMode" => "single_hold",
                //                 "reduceOnly" => false,
                //                 "cTime" => "1729166603306",
                //                 "uTime" => null
                //             }
                //         )
                //     }
                //
                $response = $this->privateGetApiMixV1PlanCurrentPlan ($this->extend($request, $params));
            } else {
                $response = $this->privateGetApiMixV1OrderCurrent ($this->extend($request, $params));
            }
        } elseif ($isTrigger) {
            throw new ArgumentsRequired($this->id . ' ' . $methodName . '() requires a $symbol argument for swap trigger orders');
        } else {
            if ($productType === null) {
                $productType = 'umcbl';
            }
            $request = array(
                'productType' => $productType, // is mandatory for current endpoint (all open non-trigger orders)
            );
            $marginCoin = null;
            $marginCoin = $this->handle_option($methodName, 'marginCoin', $marginCoin);
            if ($marginCoin !== null) {
                $request['marginCoin'] = $marginCoin;
            }
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1728127869097,
            //         "data" => array(
            //             {
            //                 "symbol" => "ETHUSDT_UMCBL",
            //                 "size" => 0.02,
            //                 "orderId" => "1226422495431974913",
            //                 "clientOid" => "1226422495457140736",
            //                 "filledQty" => 0.00,
            //                 "fee" => 0E-8,
            //                 "price" => 500.00,
            //                 "state" => "new",
            //                 "side" => "buy_single",
            //                 "timeInForce" => "normal",
            //                 "totalProfits" => 0E-8,
            //                 "posSide" => "long",
            //                 "marginCoin" => "USDT",
            //                 "filledAmount" => 0.0000,
            //                 "orderType" => "limit",
            //                 "leverage" => "5",
            //                 "marginMode" => "crossed",
            //                 "reduceOnly" => false,
            //                 "enterPointSource" => "API",
            //                 "tradeSide" => "buy_single",
            //                 "holdMode" => "single_hold",
            //                 "orderSource" => "normal",
            //                 "cTime" => "1728127829422",
            //                 "uTime" => "1728127830980"
            //             }
            //         )
            //     }
            //
            $response = $this->privateGetApiMixV1OrderMarginCoinCurrent ($this->extend($request, $params));
        }
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_orders($data, $market, $since, $limit);
    }

    public function fetch_canceled_and_closed_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on multiple canceled and closed orders made by the user
         *
         * @see https://coincatch.github.io/github.io/en/spot/#get-order-list
         * @see https://coincatch.github.io/github.io/en/spot/#get-history-plan-orders
         * @see https://coincatch.github.io/github.io/en/mix/#get-history-orders
         * @see https://coincatch.github.io/github.io/en/mix/#get-producttype-history-orders
         * @see https://coincatch.github.io/github.io/en/mix/#get-history-plan-orders-tpsl
         *
         * @param {string} $symbol *is mandatory* 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
         * @param {boolean} [$params->trigger] true if fetching trigger orders (default false)
         * @param {string} [$params->isPlan] *swap only* 'plan' or 'profit_loss' ('plan' (default) for trigger (plan) orders, 'profit_loss' for stop-loss and take-profit orders)
         * @param {string} [$params->type] 'spot' or 'swap' - the type of the $market to fetch entries for (default 'spot')
         * @param {string} [$params->productType] *swap only* 'umcbl' or 'dmcbl' - the product type of the $market to fetch entries for (default 'umcbl')
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $methodName = 'fetchCanceledAndClosedOrders';
        $this->load_markets();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        $marketType = 'spot';
        list($marketType, $params) = $this->handle_market_type_and_params($methodName, $market, $params, $marketType);
        $params['methodName'] = $methodName;
        if ($marketType === 'spot') {
            return $this->fetch_canceled_and_closed_spot_orders($symbol, $since, $limit, $params);
        } elseif ($marketType === 'swap') {
            return $this->fetch_canceled_and_closed_swap_orders($symbol, $since, $limit, $params);
        } else {
            throw new NotSupported($this->id . ' ' . $methodName . '() is not supported for ' . $marketType . ' type of markets');
        }
    }

    public function fetch_canceled_and_closed_spot_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * @ignore
         * fetches information on multiple canceled and closed orders made by the user on spot markets
         *
         * @see https://coincatch.github.io/github.io/en/spot/#get-order-history
         * @see https://coincatch.github.io/github.io/en/spot/#get-history-plan-orders
         *
         * @param {string} $symbol *is mandatory* 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] *for trigger orders only* the latest time in ms to fetch orders for
         * @param {boolean} [$params->trigger] true if fetching trigger orders (default false)
         * @param {string} [$params->lastEndId] *for trigger orders only* the last order id to fetch entries after
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $methodName = 'fetchCanceledAndClosedSpotOrders';
        list($methodName, $params) = $this->handle_param_string($params, 'methodName', $methodName);
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' ' . $methodName . ' () requires a $symbol argument for spot markets');
        }
        $maxLimit = 500;
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $requestLimit = $limit;
        $isTrigger = false;
        list($isTrigger, $params) = $this->handle_option_and_params_2($params, $methodName, 'trigger', 'stop', $isTrigger);
        $result = null;
        if ($isTrigger) {
            $until = null;
            list($until, $params) = $this->handle_option_and_params($params, $methodName, 'until', $until);
            // $now = $this->milliseconds();
            $requestSince = $since;
            $interval = 90 * 24 * 60 * 60 * 1000; // startTime and endTime $interval cannot be greater than 90 days
            $now = $this->milliseconds();
            // both $since and $until are required for trigger orders
            if (($until === null) && ($requestSince === null)) {
                $requestSince = $now - $interval;
                $until = $now;
            } elseif ($until !== null) {
                $requestSince = $until - $interval;
            } else { // if $since is defined
                $until = $since . $interval;
            }
            $request['startTime'] = $requestSince;
            $request['endTime'] = $until;
            if ($requestLimit === null) {
                $requestLimit = $maxLimit;
            }
            $request['pageSize'] = $requestLimit;
            $response = $this->privatePostApiSpotV1PlanHistoryPlan ($this->extend($request, $params));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1728668998002,
            //         "data" => {
            //             "nextFlag" => false,
            //             "endId" => 1228669617606991872,
            //             "orderList" => array(
            //                 {
            //                     "orderId" => "1228669617606991872",
            //                     "clientOid" => "1228669617573437440",
            //                     "symbol" => "ETHUSDT_SPBL",
            //                     "size" => "50",
            //                     "executePrice" => "0",
            //                     "triggerPrice" => "4000",
            //                     "status" => "cancel",
            //                     "orderType" => "market",
            //                     "side" => "sell",
            //                     "triggerType" => "fill_price",
            //                     "enterPointSource" => "API",
            //                     "placeType" => null,
            //                     "cTime" => "1728663585092",
            //                     "uTime" => "1728666719223"
            //                 }
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $result = $this->safe_list($data, 'orderList', array());
        } else {
            if ($since !== null) {
                $request['after'] = $since;
                $requestLimit = $maxLimit;
            }
            if ($requestLimit !== null) {
                $request['limit'] = $requestLimit;
            }
            $response = $this->privatePostApiSpotV1TradeHistory ($this->extend($request, $params));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1725963777690,
            //         "data" => array(
            //             {
            //                 "accountId" => "1002820815393",
            //                 "symbol" => "ETHUSDT_SPBL",
            //                 "orderId" => "1217143186968068096",
            //                 "clientOrderId" => "8fa3eb89-2377-4519-a199-35d5db9ed262",
            //                 "price" => "0",
            //                 "quantity" => "10.0000000000000000",
            //                 "orderType" => "market",
            //                 "side" => "buy",
            //                 "status" => "full_fill",
            //                 "fillPrice" => "2340.5500000000000000",
            //                 "fillQuantity" => "0.0042000000000000",
            //                 "fillTotalAmount" => "9.8303100000000000",
            //                 "enterPointSource" => "API",
            //                 "feeDetail" => "array(
            //                     \"ETH\" => array(
            //                         \"deduction\" => false,
            //                         \"feeCoinCode\" => \"ETH\",
            //                         \"totalDeductionFee\" => 0,
            //                         \"totalFee\" => -0.0000042000000000
            //                     ),
            //                     \"newFees\" => array(
            //                         \"c\" => 0,
            //                         \"d\" => 0,
            //                         \"deduction\" => false,
            //                         \"r\" => -0.0000042,
            //                         \"t\" => -0.0000042,
            //                         \"totalDeductionFee\" => 0
            //                     }
            //                 )",
            //                 "orderSource" => "market",
            //                 "cTime" => "1725915469877"
            //             ),
            //             ...
            //         )
            //     }
            //
            $parsedResponse = json_decode($response, $as_associative_array = true); // the $response is not a standard JSON
            $result = $this->safe_list($parsedResponse, 'data', array());
        }
        return $this->parse_orders($result, $market, $since, $limit);
    }

    public function fetch_canceled_and_closed_swap_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * @ignore
         * fetches information on multiple canceled and closed orders made by the user on swap markets
         *
         * @see https://coincatch.github.io/github.io/en/mix/#get-history-orders
         * @see https://coincatch.github.io/github.io/en/mix/#get-producttype-history-orders
         * @see https://coincatch.github.io/github.io/en/mix/#get-history-$plan-orders-tpsl
         *
         * @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
         * @param {boolean} [$params->trigger] true if fetching trigger orders (default false)
         * @param {string} [$params->isPlan] *swap only* 'plan' or 'profit_loss' ('plan' (default) for trigger ($plan) orders, 'profit_loss' for stop-loss and take-profit orders)
         * @param {string} [$params->productType] *swap only* 'umcbl' or 'dmcbl' - the product type of the $market to fetch entries for (default 'umcbl')
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $methodName = 'fetchCanceledAndClosedSwapOrders';
        list($methodName, $params) = $this->handle_param_string($params, 'methodName', $methodName);
        $requestSince = $since;
        $until = null;
        list($until, $params) = $this->handle_option_and_params($params, $methodName, 'until', $until);
        $now = $this->milliseconds();
        // $since and $until are mandatory
        // they should be within 90 days $interval
        $interval = 90 * 24 * 60 * 60 * 1000;
        if (($until === null) && ($requestSince === null)) {
            $requestSince = $now - $interval;
            $until = $now;
        } elseif ($until !== null) {
            $requestSince = $until - $interval;
        } else { // if $since is defined
            $until = $since . $interval;
        }
        $request = array(
            'startTime' => $requestSince,
            'endTime' => $until,
        );
        if ($limit !== null) {
            $request['pageSize'] = $limit;
        }
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        $productType = $this->handle_option($methodName, 'productType');
        $isTrigger = false;
        list($isTrigger, $params) = $this->handle_option_and_params_2($params, $methodName, 'trigger', 'stop', $isTrigger);
        $plan = null;
        list($plan, $params) = $this->handle_option_and_params($params, $methodName, 'isPlan', $plan);
        $response = null;
        $result = null;
        if (($isTrigger) || ($plan !== null)) {
            if ($plan !== null) {
                $request['isPlan'] = $plan;
            }
            if ($productType !== null) {
                $request['productType'] = $productType;
            }
            $response = $this->privateGetApiMixV1PlanHistoryPlan ($this->extend($request, $params));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1729174716526,
            //         "data" => array(
            //             {
            //                 "orderId" => "1230763430987104257",
            //                 "clientOid" => "1230763431003881472",
            //                 "executeOrderId" => "",
            //                 "symbol" => "ETHUSDT_UMCBL",
            //                 "marginCoin" => "USDT",
            //                 "size" => "0.03",
            //                 "executePrice" => "0",
            //                 "triggerPrice" => "2000",
            //                 "status" => "cancel",
            //                 "orderType" => "market",
            //                 "planType" => "loss_plan",
            //                 "side" => "sell_single",
            //                 "triggerType" => "fill_price",
            //                 "presetTakeProfitPrice" => "0",
            //                 "presetTakeLossPrice" => "0",
            //                 "rangeRate" => null,
            //                 "enterPointSource" => "SYS",
            //                 "tradeSide" => "sell_single",
            //                 "holdMode" => "single_hold",
            //                 "reduceOnly" => true,
            //                 "executeTime" => "1729173770776",
            //                 "executeSize" => "0",
            //                 "cTime" => "1729162789103",
            //                 "uTime" => "1729173770776"
            //             }
            //         )
            //     }
            //
            $result = $this->safe_list($response, 'data', array());
        } else {
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['symbol'] = $market['id'];
                $response = $this->privateGetApiMixV1OrderHistory ($this->extend($request, $params));
            } else {
                if ($productType === null) {
                    $productType = 'umcbl'; // is mandatory for current endpoint
                }
                $request['productType'] = $productType;
                $response = $this->privateGetApiMixV1OrderHistoryProductType ($this->extend($request, $params));
            }
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1728129807637,
            //         "data" => {
            //             "nextFlag" => false,
            //             "endId" => "1221413696648339457",
            //             "orderList" => array(
            //                 {
            //                     "symbol" => "ETHUSD_DMCBL",
            //                     "size" => 0.1,
            //                     "orderId" => "1225467075288719360",
            //                     "clientOid" => "1225467075288719361",
            //                     "filledQty" => 0.1,
            //                     "fee" => -0.00005996,
            //                     "price" => null,
            //                     "priceAvg" => 2362.03,
            //                     "state" => "filled",
            //                     "side" => "burst_close_long",
            //                     "timeInForce" => "normal",
            //                     "totalProfits" => -0.00833590,
            //                     "posSide" => "long",
            //                     "marginCoin" => "ETH",
            //                     "filledAmount" => 236.20300000,
            //                     "orderType" => "market",
            //                     "leverage" => "12",
            //                     "marginMode" => "fixed",
            //                     "reduceOnly" => true,
            //                     "enterPointSource" => "SYS",
            //                     "tradeSide" => "burst_close_long",
            //                     "holdMode" => "double_hold",
            //                     "orderSource" => "market",
            //                     "cTime" => "1727900039503",
            //                     "uTime" => "1727900039576"
            //                 }
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $result = $this->safe_list($data, 'orderList', array());
        }
        return $this->parse_orders($result, $market);
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * cancels an open order
         *
         * @see https://coincatch.github.io/github.io/en/spot/#cancel-order-v2
         * @see https://coincatch.github.io/github.io/en/spot/#cancel-plan-order
         * @see https://coincatch.github.io/github.io/en/mix/#cancel-order
         * @see https://coincatch.github.io/github.io/en/mix/#cancel-plan-order-tpsl
         *
         * @param {string} $id order $id
         * @param {string} $symbol unified $symbol of the $market the order was made in
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->clientOrderId] a unique $id for the order that can be used alternative for the $id
         * @param {bool} [$params->trigger] true for canceling a $trigger order (default false)
         * @param {bool} [$params->stop] *swap only* an alternative for $trigger param
         * @param {string} [$params->planType] *swap $trigger only* the type of the plan order to cancel => 'profit_plan' - profit order, 'loss_plan' - loss order, 'normal_plan' - plan order, 'pos_profit' - position profit, 'pos_loss' - position loss, 'moving_plan' - Trailing TP/SL, 'track_plan' - Trailing Stop
         * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $methodName = 'cancelOrder';
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' ' . $methodName . ' () requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array();
        $clientOrderId = null;
        list($clientOrderId, $params) = $this->handle_param_string($params, 'clientOrderId');
        if (($id === null) && ($clientOrderId === null)) {
            throw new ArgumentsRequired($this->id . ' ' . $methodName . ' () requires an $id argument or $clientOrderId parameter');
        }
        if ($clientOrderId !== null) {
            $request['clientOid'] = $clientOrderId;
        } else {
            $request['orderId'] = $id;
        }
        $marketType = $market['type'];
        $trigger = false;
        list($trigger, $params) = $this->handle_option_and_params_2($params, $methodName, 'trigger', 'stop', $trigger);
        $response = null;
        if (!$trigger || ($marketType !== 'spot')) {
            $request['symbol'] = $market['id'];
        }
        if ($marketType === 'spot') {
            if ($trigger) {
                $response = $this->privatePostApiSpotV1PlanCancelPlan ($this->extend($request, $params));
            } else {
                $response = $this->privatePostApiSpotV1TradeCancelOrderV2 ($this->extend($request, $params));
            }
        } elseif ($marketType === 'swap') {
            $planType = null;
            list($planType, $params) = $this->handle_option_and_params($params, $methodName, 'planType', $planType);
            $request['marginCoin'] = $market['settleId'];
            if (($trigger) || ($planType !== null)) {
                if ($planType === null) {
                    throw new ArgumentsRequired($this->id . ' ' . $methodName . ' () requires a $planType parameter for swap $trigger orders ("profit_plan" - profit order, "loss_plan" - loss order, "normal_plan" - plan order, "pos_profit" - position profit, "pos_loss" - position loss, "moving_plan" - Trailing TP/SL, "track_plan" - Trailing Stop)');
                }
                $request['planType'] = $planType;
                $response = $this->privatePostApiMixV1PlanCancelPlan ($this->extend($request, $params));
            } else {
                $response = $this->privatePostApiMixV1OrderCancelOrder ($this->extend($request, $params));
            }
        } else {
            throw new NotSupported($this->id . ' ' . $methodName . '() is not supported for ' . $marketType . ' type of markets');
        }
        $data = $this->safe_dict($response, 'data', array());
        return $this->parse_order($data, $market);
    }

    public function cancel_all_orders(?string $symbol = null, $params = array ()) {
        /**
         * cancels all open orders
         *
         * @see https://coincatch.github.io/github.io/en/spot/#cancel-all-orders
         * @see https://coincatch.github.io/github.io/en/spot/#batch-cancel-plan-orders
         * @see https://coincatch.github.io/github.io/en/mix/#batch-cancel-$order
         * @see https://coincatch.github.io/github.io/en/mix/#cancel-$order-by-$symbol
         * @see https://coincatch.github.io/github.io/en/mix/#cancel-plan-$order-tpsl-by-$symbol
         * @see https://coincatch.github.io/github.io/en/mix/#cancel-all-$trigger-$order-tpsl
         *
         * @param {string} [$symbol] unified $symbol of the $market the orders were made in
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->type] 'spot' or 'swap' - the type of the $market to cancel orders for (default 'spot')
         * @param {bool} [$params->trigger] true for canceling a $trigger orders (default false)
         * @param {string} [$params->productType] *swap only (if $symbol is not provided* 'umcbl' or 'dmcbl' - the product type of the $market to cancel orders for (default 'umcbl')
         * @param {string} [$params->marginCoin] *mandatory for swap non-$trigger dmcb (if $symbol is not provided)* the margin coin of the $market to cancel orders for
         * @param {string} [$params->planType] *swap $trigger only* the type of the plan $order to cancel => 'profit_plan' - profit $order, 'loss_plan' - loss $order, 'normal_plan' - plan $order, 'pos_profit' - position profit, 'pos_loss' - position loss, 'moving_plan' - Trailing TP/SL, 'track_plan' - Trailing Stop
         * @return {array} $response from the exchange
         */
        $methodName = 'cancelAllOrders';
        $this->load_markets();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        $request = array();
        $marketType = 'spot';
        list($marketType, $params) = $this->handle_market_type_and_params($methodName, $market, $params, $marketType);
        $trigger = false;
        list($trigger, $params) = $this->handle_option_and_params_2($params, $methodName, 'trigger', 'stop', $trigger);
        $response = null;
        if ($marketType === 'spot') {
            if ($trigger) {
                if ($symbol !== null) {
                    $request['symbols'] = [ $market['id'] ];
                }
                $response = $this->privatePostApiSpotV1PlanBatchCancelPlan ($this->extend($request, $params));
                //
                //     {
                //         "code" => "00000",
                //         "msg" => "success",
                //         "requestTime" => 1728670464735,
                //         "data" => array(
                //             {
                //                 "orderId" => "1228661660806787072",
                //                 "clientOid" => "1228661660752261120",
                //                 "result" => true
                //             }
                //         )
                //     }
                //
                $data = $this->safe_list($response, 'data', array());
                return $this->parse_orders($data, $market);
            } else {
                if ($symbol === null) {
                    throw new ArgumentsRequired($this->id . ' ' . $methodName . ' () requires a $symbol argument for spot non-$trigger orders');
                }
                $request['symbol'] = $market['id'];
                //
                //     {
                //         "code" => "00000",
                //         "msg" => "success",
                //         "requestTime" => 1725989560461,
                //         "data" => "ETHUSDT_SPBL"
                //     }
                //
                $response = $this->privatePostApiSpotV1TradeCancelSymbolOrder ($this->extend($request, $params));
            }
        } elseif ($marketType === 'swap') {
            $productType = 'umcbl';
            if ($symbol !== null) {
                $request['symbol'] = $market['id'];
            } else {
                $productType = $this->handle_option($methodName, 'productType', $productType);
                $request['productType'] = $productType; // we need either $symbol or $productType
            }
            $planType = null;
            list($planType, $params) = $this->handle_option_and_params($params, $methodName, 'planType', $planType);
            if (($trigger) || ($planType !== null)) { // if $trigger or stop-loss/take-profit orders
                if ($planType === null) {
                    throw new ArgumentsRequired($this->id . ' ' . $methodName . ' () requires a $planType parameter for swap $trigger orders ("profit_plan" - profit $order, "loss_plan" - loss $order, "normal_plan" - plan $order, "pos_profit" - position profit, "pos_loss" - position loss, "moving_plan" - Trailing TP/SL, "track_plan" - Trailing Stop)');
                }
                $request['planType'] = $planType;
                if ($symbol !== null) {
                    $response = $this->privatePostApiMixV1PlanCancelSymbolPlan ($this->extend($request, $params));
                } else {
                    $response = $this->privatePostApiMixV1PlanCancelAllPlan ($this->extend($request, $params));
                }
            } elseif ($symbol !== null) { // if non-$trigger orders and $symbol is provided
                $request['marginCoin'] = $market['settleId'];
                $response = $this->privatePostApiMixV1OrderCancelSymbolOrders ($this->extend($request, $params));
            } else { // if non-$trigger orders and $symbol is not provided
                $marginCoin = null;
                if ($productType === 'umcbl') {
                    $marginCoin = 'USDT';
                } else {
                    list($marginCoin, $params) = $this->handle_option_and_params($params, $methodName, 'marginCoin', $marginCoin);
                    if ($marginCoin === null) {
                        throw new ArgumentsRequired($this->id . ' ' . $methodName . ' () requires a $marginCoin parameter for dmcbl product type');
                    }
                }
                $request['marginCoin'] = $marginCoin;
                $response = $this->privatePostApiMixV1OrderCancelAllOrders ($this->extend($request, $params));
            }
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1729104940774,
            //         "data" => {
            //             "result" => true,
            //             "order_ids" => array( "1230500426827522049" ),
            //             "client_order_ids" => array( "1230500426898825216" ),
            //             "fail_infos" => array()
            //         }
            //     }
            //
            $result = $this->get_result_from_batch_canceling_swap_orders($response);
            return $this->parse_orders($result, $market);
        } else {
            throw new NotSupported($this->id . ' ' . $methodName . '() is not supported for ' . $marketType . ' type of markets');
        }
        $order = $this->safe_order($response);
        $order['info'] = $response;
        return array( $order );
    }

    public function cancel_orders(array $ids, ?string $symbol = null, $params = array ()) {
        /**
         * cancel multiple non-trigger orders
         *
         * @see https://coincatch.github.io/github.io/en/spot/#cancel-order-in-batch-v2-single-instruments
         *
         * @param {string[]} $ids order $ids
         * @param {string} $symbol *is mandatory* unified $market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string[]} [$params->clientOrderIds] client order $ids
         * @return {array} an list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $methodName = 'cancelOrders';
        // only non-trigger and not tp/sl orders can be canceled via cancelOrders
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' ' . $methodName . '() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $marketType = $market['type'];
        $clientOrderIds = $this->safe_list($params, 'clientOrderIds');
        if ($clientOrderIds !== null) {
            $request['clientOids'] = $clientOrderIds;
            $params = $this->omit($params, 'clientOrderIds');
        } elseif ($ids === null) {
            throw new ArgumentsRequired($this->id . ' ' . $methodName . '() requires either $ids argument or $clientOrderIds parameter');
        } else {
            $request['orderIds'] = $ids;
        }
        $response = null;
        $result = null;
        if ($marketType === 'spot') {
            $response = $this->privatePostApiSpotV1TradeCancelBatchOrdersV2 ($this->extend($request));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1726491486352,
            //         "data" => {
            //             "resultList" => array(
            //                 {
            //                     "orderId" => "1219555778395160576",
            //                     "clientOrderId" => "e229d70a-bb16-4633-a45c-d7f4d3b5d2cf"
            //                 }
            //             ),
            //             "failure" => array(
            //                 {
            //                     "orderId" => "123124124",
            //                     "clientOrderId" => null,
            //                     "errorMsg" => "The order does not exist",
            //                     "errorCode" => "43001"
            //                 }
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $result = $this->safe_list($data, 'resultList', array());
        } elseif ($marketType === 'swap') {
            $request['marginCoin'] = $market['settleId'];
            $response = $this->privatePostApiMixV1OrderCancelBatchOrders ($this->extend($request, $params));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1729101962321,
            //         "data" => {
            //             "result" => true,
            //             "symbol" => "ETHUSDT_UMCBL",
            //             "order_ids" => array( "1226441551501418496", "1230506854262857729" ),
            //             "client_order_ids" => array(),
            //             "fail_infos" => array()
            //         }
            //     }
            //
            $result = $this->get_result_from_batch_canceling_swap_orders($response);
        } else {
            throw new NotSupported($this->id . ' ' . $methodName . '() is not supported for ' . $marketType . ' type of markets');
        }
        return $this->parse_orders($result, $market);
    }

    public function get_result_from_batch_canceling_swap_orders($response) {
        $data = $this->safe_dict($response, 'data', array());
        $result = array();
        $orderIds = $this->safe_value($data, 'order_ids', array());
        for ($i = 0; $i < count($orderIds); $i++) {
            $orderId = $orderIds[$i];
            $resultItem = array(
                'orderId' => $orderId,
            );
            $result[] = $resultItem;
        }
        return $result;
    }

    public function parse_order($order, $market = null): array {
        //
        // createOrder spot
        //     {
        //         "orderId" => "1217143186968068096",
        //         "clientOrderId" => "8fa3eb89-2377-4519-a199-35d5db9ed262"
        //     }
        //
        // createOrder swap
        //     {
        //         "clientOid" => "1225791137701519360",
        //         "orderId" => "1225791137697325056"
        //     }
        //
        // privatePostApiSpotV1TradeOrderInfo, privatePostApiSpotV1TradeHistory
        //     {
        //         "accountId" => "1002820815393",
        //         "symbol" => "ETHUSDT_SPBL",
        //         "orderId" => "1217143186968068096",
        //         "clientOrderId" => "8fa3eb89-2377-4519-a199-35d5db9ed262",
        //         "price" => "0",
        //         "quantity" => "10.0000000000000000",
        //         "orderType" => "market",
        //         "side" => "buy",
        //         "status" => "full_fill",
        //         "fillPrice" => "2340.5500000000000000",
        //         "fillQuantity" => "0.0042000000000000",
        //         "fillTotalAmount" => "9.8303100000000000",
        //         "enterPointSource" => "API",
        //         "feeDetail" => "{
        //             \"ETH\" => array(
        //                 \"deduction\" => false,
        //                 \"feeCoinCode\" => \"ETH\",
        //                 \"totalDeductionFee\" => 0,
        //                 \"totalFee\" => -0.0000042000000000),
        //                 \"newFees\" => array(
        //                     \"c\" => 0,
        //                     \"d\" => 0,
        //                     \"deduction\" => false,
        //                     \"r\" => -0.0000042,
        //                     \"t\" => -0.0000042,
        //                     \"totalDeductionFee\" => 0
        //             }
        //         )",
        //         "orderSource" => "market",
        //         "cTime" => "1725915469877"
        //     }
        //
        // privatePostApiMixV1OrderDetail, privateGetApiMixV1OrderMarginCoinCurrent
        //     {
        //         "symbol" => "ETHUSDT_UMCBL",
        //         "size" => 0.01,
        //         "orderId" => "1225791137697325056",
        //         "clientOid" => "1225791137701519360",
        //         "filledQty" => 0.01,
        //         "fee" => -0.01398864,
        //         "price" => null,
        //         "priceAvg" => 2331.44,
        //         "state" => "filled",
        //         "side" => "close_long",
        //         "timeInForce" => "normal",
        //         "totalProfits" => -2.23680000,
        //         "posSide" => "long",
        //         "marginCoin" => "USDT",
        //         "filledAmount" => 23.3144,
        //         "orderType" => "market",
        //         "leverage" => "5",
        //         "marginMode" => "crossed",
        //         "reduceOnly" => true,
        //         "enterPointSource" => "API",
        //         "tradeSide" => "close_long",
        //         "holdMode" => "double_hold",
        //         "orderSource" => "market",
        //         "cTime" => "1727977302003",
        //         "uTime" => "1727977303604"
        //     }
        //
        // privatePostApiSpotV1TradeOpenOrders
        //     {
        //         "accountId" => "1002820815393",
        //         "symbol" => "ETHUSDT_SPBL",
        //         "orderId" => "1217347655911653376",
        //         "clientOrderId" => "c57c07d1-bd00-4167-95e2-9b22a55fbc28",
        //         "price" => "2000.0000000000000000",
        //         "quantity" => "0.0010000000000000",
        //         "orderType" => "limit",
        //         "side" => "buy",
        //         "status" => "new",
        //         "fillPrice" => "0",
        //         "fillQuantity" => "0.0000000000000000",
        //         "fillTotalAmount" => "0.0000000000000000",
        //         "enterPointSource" => "API",
        //         "feeDetail" => "",
        //         "orderSource" => "normal",
        //         "cTime" => "1725964219072"
        //     }
        //
        // privatePostApiSpotV1PlanCurrentPlan, privatePostApiSpotV1PlanHistoryPlan
        //     {
        //         "orderId" => "1228669617606991872",
        //         "clientOid" => "1228669617573437440",
        //         "symbol" => "ETHUSDT_SPBL",
        //         "size" => "50",
        //         "executePrice" => "0",
        //         "triggerPrice" => "4000",
        //         "status" => "not_trigger",
        //         "orderType" => "market",
        //         "side" => "sell",
        //         "triggerType" => "fill_price",
        //         "enterPointSource" => "API",
        //         "placeType" => null,
        //         "cTime" => "1728663585092",
        //         "uTime" => null
        //     }
        //
        // privateGetApiMixV1PlanCurrentPlan
        //     {
        //         "orderId" => "1230779428914049025",
        //         "clientOid" => "1230779428914049024",
        //         "symbol" => "ETHUSDT_UMCBL",
        //         "marginCoin" => "USDT",
        //         "size" => "0.01",
        //         "executePrice" => "1000",
        //         "triggerPrice" => "1200",
        //         "status" => "not_trigger",
        //         "orderType" => "limit",
        //         "planType" => "normal_plan",
        //         "side" => "buy_single",
        //         "triggerType" => "fill_price",
        //         "presetTakeProfitPrice" => "4000",
        //         "presetTakeLossPrice" => "900",
        //         "rangeRate" => "",
        //         "enterPointSource" => "API",
        //         "tradeSide" => "buy_single",
        //         "holdMode" => "single_hold",
        //         "reduceOnly" => false,
        //         "cTime" => "1729166603306",
        //         "uTime" => null
        //     }
        //
        $marketId = $this->safe_string($order, 'symbol');
        $marginCoin = $this->safe_string($order, 'marginCoin');
        $market = $this->safe_market_custom($marketId, $market, $marginCoin);
        $timestamp = $this->safe_integer($order, 'cTime');
        $price = $this->omit_zero($this->safe_string_2($order, 'price', 'executePrice')); // $price is zero for $market orders
        $priceAvg = $this->omit_zero($this->safe_string($order, 'priceAvg'));
        if ($price === null) {
            $price = $priceAvg;
        }
        $type = $this->safe_string($order, 'orderType');
        $side = $this->parse_order_side($this->safe_string_lower($order, 'side'));
        $amount = $this->safe_string_2($order, 'quantity', 'size');
        $isTrigger = $this->safe_string($order, 'triggerType') !== null;
        $isMarketBuy = ($type === 'market') && ($side === 'buy');
        if (($market['spot']) && ($isMarketBuy) && (!$isTrigger)) {
            $amount = null; // cost instead of $amount is returned for $market buy spot non-trigger orders
        }
        $status = $this->safe_string_2($order, 'status', 'state');
        $feeDetailString = $this->safe_string($order, 'feeDetail');
        $fees = null;
        $feeCurrency = null;
        $feeCost = null;
        if ($feeDetailString !== null) {
            $fees = $this->parse_fee_detail_string($feeDetailString);
        } else {
            $feeCurrency = $marginCoin ? $this->safe_currency_code($marginCoin) : null;
            $feeCost = Precise::string_abs($this->safe_string($order, 'fee'));
        }
        $timeInForce = $this->parse_order_time_in_force($this->safe_string_lower($order, 'timeInForce'));
        $postOnly = null;
        if ($timeInForce !== null) {
            $postOnly = $timeInForce === 'PO';
        }
        $triggerPrice = $this->omit_zero($this->safe_string($order, 'triggerPrice'));
        $takeProfitPrice = $this->omit_zero($this->safe_string($order, 'presetTakeProfitPrice'));
        $stopLossPrice = $this->omit_zero($this->safe_string_2($order, 'presetTakeProfitPrice', 'presetTakeLossPrice'));
        $planType = $this->safe_string($order, 'planType');
        if ($planType === 'loss_plan') {
            $stopLossPrice = $triggerPrice;
        } elseif ($planType === 'profit_plan') {
            $takeProfitPrice = $triggerPrice;
        }
        return $this->safe_order(array(
            'id' => $this->safe_string($order, 'orderId'),
            'clientOrderId' => $this->safe_string_2($order, 'clientOrderId', 'clientOid'),
            'datetime' => $this->iso8601($timestamp),
            'timestamp' => $timestamp,
            'lastTradeTimestamp' => null,
            'lastUpdateTimestamp' => $this->safe_integer($order, 'uTime'),
            'status' => $this->parse_order_status($status),
            'symbol' => $market['symbol'],
            'type' => $type,
            'timeInForce' => $timeInForce,
            'side' => $side,
            'price' => $price,
            'average' => $priceAvg ? $priceAvg : $this->safe_string($order, 'fillPrice'),
            'amount' => $amount,
            'filled' => $this->safe_string_2($order, 'fillQuantity', 'filledQty'),
            'remaining' => null,
            'triggerPrice' => $triggerPrice,
            'takeProfitPrice' => $takeProfitPrice,
            'stopLossPrice' => $stopLossPrice,
            'cost' => $this->safe_string_2($order, 'fillTotalAmount', 'filledAmount'),
            'trades' => null,
            'fee' => array(
                'currency' => $feeCurrency,
                'cost' => $feeCost,
            ),
            'fees' => $fees,
            'reduceOnly' => $this->safe_bool($order, 'reduceOnly'),
            'postOnly' => $postOnly,
            'info' => $order,
        ), $market);
    }

    public function parse_order_status(?string $status): ?string {
        $satuses = array(
            'not_trigger' => 'open',
            'init' => 'open',
            'new' => 'open',
            'partially_filled' => 'open',
            'full_fill' => 'closed',
            'filled' => 'closed',
            'cancel' => 'canceled',
            'canceled' => 'canceled',
            'cancelled' => 'canceled',
        );
        return $this->safe_string($satuses, $status, $status);
    }

    public function parse_order_side(?string $side): ?string {
        $sides = array(
            'buy' => 'buy',
            'sell' => 'sell',
            'open_long' => 'buy',
            'open_short' => 'sell',
            'close_long' => 'sell',
            'close_short' => 'buy',
            'reduce_close_long' => 'sell',
            'reduce_close_short' => 'buy',
            'offset_close_long' => 'sell',
            'offset_close_short' => 'buy',
            'burst_close_long' => 'sell',
            'burst_close_short' => 'buy',
            'delivery_close_long' => 'sell',
            'delivery_close_short' => 'buy',
            'buy_single' => 'buy',
            'sell_single' => 'sell',
        );
        return $this->safe_string($sides, $side, $side);
    }

    public function parse_order_time_in_force(?string $timeInForce): ?string {
        $timeInForces = array(
            'normal' => 'GTC',
            'post_only' => 'PO',
            'iok' => 'IOC',
            'fok' => 'FOK',
        );
        return $this->safe_string($timeInForces, $timeInForce, $timeInForce);
    }

    public function parse_fee_detail_string(?string $feeDetailString) {
        $result = array();
        $feeDetail = $this->parse_json($feeDetailString);
        if ($feeDetail) {
            $keys = is_array($feeDetail) ? array_keys($feeDetail) : array();
            for ($i = 0; $i < count($keys); $i++) {
                $currencyId = $this->safe_string($keys, $i);
                if (is_array($this->currencies_by_id) && array_key_exists($currencyId, $this->currencies_by_id)) {
                    $currency = $this->safe_currency_code($currencyId);
                    $feeEntry = $this->safe_dict($feeDetail, $currencyId, array());
                    $amount = Precise::string_abs($this->safe_string($feeEntry, 'totalFee'));
                    $result[] = array(
                        'currency' => $currency,
                        'amount' => $amount,
                    );
                }
            }
        }
        return $result;
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all trades made by the user
         *
         * @see https://coincatch.github.io/github.io/en/spot/#get-transaction-details
         * @see https://coincatch.github.io/github.io/en/mix/#get-order-fill-detail
         * @see https://coincatch.github.io/github.io/en/mix/#get-producttype-order-fill-detail
         *
         * @param {string} $symbol *is mandatory* unified $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch trades for
         * @param {int} [$limit] the maximum amount of trades to fetch
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] *swap markets only* the latest time in ms to fetch trades for, only supports the last 30 days timeframe
         * @param {string} [$params->lastEndId] *swap markets only* query the $data after this tradeId
         * @return {Trade[]} a list of {@link https://github.com/ccxt/ccxt/wiki/Manual#trade-structure trade structures}
         */
        $methodName = 'fetchMyTrades';
        list($methodName, $params) = $this->handle_param_string($params, 'methodName', $methodName);
        $this->load_markets();
        $market = null;
        $marketType = 'spot';
        $request = array();
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $marketType = $market['type'];
            $request['symbol'] = $market['id'];
        } else {
            list($marketType, $params) = $this->handle_market_type_and_params($methodName, $market, $params, $marketType);
            if ($marketType === 'spot') {
                throw new ArgumentsRequired($this->id . ' ' . $methodName . '() requires a $symbol argument for spot markets');
            }
        }
        $response = null;
        $requestLimit = $limit;
        if ($marketType === 'spot') {
            $maxSpotLimit = 500;
            if ($since !== null) {
                $requestLimit = $maxSpotLimit;
            }
            if ($requestLimit !== null) {
                $request['limit'] = $requestLimit;
            }
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1725968747299,
            //         "data" => array(
            //             array(
            //                 "accountId" => "1002820815393",
            //                 "symbol" => "ETHUSDT_SPBL",
            //                 "orderId" => "1217143186968068096",
            //                 "fillId" => "1217143193356505089",
            //                 "orderType" => "market",
            //                 "side" => "buy",
            //                 "fillPrice" => "2340.55",
            //                 "fillQuantity" => "0.0042",
            //                 "fillTotalAmount" => "9.83031",
            //                 "feeCcy" => "ETH",
            //                 "fees" => "-0.0000042",
            //                 "takerMakerFlag" => "taker",
            //                 "cTime" => "1725915471400"
            //             ),
            //             ...
            //         )
            //     }
            //
            $response = $this->privatePostApiSpotV1TradeFills ($this->extend($request, $params));
        } elseif ($marketType === 'swap') {
            if ($since !== null) {
                $params['startTime'] = $since;
            } else {
                $params['startTime'] = 0; // mandatory
            }
            $until = null;
            list($until, $params) = $this->handle_option_and_params($params, $methodName, 'until');
            if ($until !== null) {
                $request['endTime'] = $until;
            } else {
                $request['endTime'] = $this->milliseconds(); // mandatory
            }
            if ($symbol !== null) {
                //
                //     {
                //         "code" => "00000",
                //         "msg" => "success",
                //         "requestTime" => 1728306590704,
                //         "data" => array(
                //             {
                //                 "tradeId" => "1221355735285014530",
                //                 "symbol" => "ETHUSDT_UMCBL",
                //                 "orderId" => "1221355728716259329",
                //                 "price" => "2555.12",
                //                 "sizeQty" => "0.01",
                //                 "fee" => "-0.01533072",
                //                 "side" => "open_long",
                //                 "fillAmount" => "25.5512",
                //                 "profit" => "0",
                //                 "enterPointSource" => "API",
                //                 "tradeSide" => "open_long",
                //                 "holdMode" => "double_hold",
                //                 "takerMakerFlag" => "taker",
                //                 "cTime" => "1726919819661"
                //             }
                //         )
                //     }
                //
                $response = $this->privateGetApiMixV1OrderFills ($this->extend($request, $params));
            } else {
                $productType = 'umcbl';
                $productType = $this->handle_option($methodName, 'productType', $productType);
                $request['productType'] = $productType;
                //
                //     {
                //         "code" => "00000",
                //         "msg" => "success",
                //         "requestTime" => 1728306372044,
                //         "data" => array(
                //             array(
                //                 "tradeId" => "1225467075440189441",
                //                 "symbol" => "ETHUSD_DMCBL",
                //                 "orderId" => "1225467075288719360",
                //                 "price" => "2362.03",
                //                 "sizeQty" => "0.1",
                //                 "fee" => "-0.00005996",
                //                 "side" => "burst_close_long",
                //                 "fillAmount" => "236.203",
                //                 "profit" => "-0.0083359",
                //                 "enterPointSource" => "SYS",
                //                 "tradeSide" => "burst_close_long",
                //                 "holdMode" => "double_hold",
                //                 "takerMakerFlag" => "taker",
                //                 "cTime" => "1727900039539"
                //             ),
                //             ...
                //         )
                //     }
                //
                $response = $this->privateGetApiMixV1OrderAllFills ($this->extend($request, $params));
            }
        } else {
            throw new NotSupported($this->id . ' ' . $methodName . '() is not supported for ' . $marketType . ' type of markets');
        }
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_trades($data, $market, $since, $limit);
    }

    public function fetch_order_trades(string $id, ?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all the trades made from a single order
         *
         * @see https://coincatch.github.io/github.io/en/spot/#get-transaction-details
         *
         * @param {string} $id order $id
         * @param {string} $symbol unified market $symbol
         * @param {int} [$since] the earliest time in ms to fetch trades for
         * @param {int} [$limit] the maximum number of trades to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?$id=trade-structure trade structures~
         */
        $methodName = 'fetchOrderTrades';
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' ' . $methodName . ' () requires a $symbol argument');
        }
        $request = array(
            'orderId' => $id,
            'methodName' => $methodName,
        );
        return $this->fetch_my_trades($symbol, $since, $limit, $this->extend($request, $params));
    }

    public function fetch_margin_mode(string $symbol, $params = array ()): array {
        /**
         * fetches the margin mode of the trading pair
         *
         * @see https://coincatch.github.io/github.io/en/mix/#get-single-account
         *
         * @param {string} $symbol unified $symbol of the $market to fetch the margin mode for
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=margin-mode-structure margin mode structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            'marginCoin' => $market['settleId'],
        );
        $response = $this->privateGetApiMixV1AccountAccount ($this->extend($request, $params));
        //
        //     {
        //         "code" => "00000",
        //         "msg" => "success",
        //         "requestTime" => 1726669633799,
        //         "data" => {
        //             "marginCoin" => "ETH",
        //             "locked" => "0",
        //             "available" => "0.01",
        //             "crossMaxAvailable" => "0.01",
        //             "fixedMaxAvailable" => "0.01",
        //             "maxTransferOut" => "0.01",
        //             "equity" => "0.01",
        //             "usdtEquity" => "22.97657025",
        //             "btcEquity" => "0.000386195288",
        //             "crossRiskRate" => "0",
        //             "crossMarginLeverage" => 100,
        //             "fixedLongLeverage" => 100,
        //             "fixedShortLeverage" => 100,
        //             "marginMode" => "crossed",
        //             "holdMode" => "double_hold",
        //             "unrealizedPL" => "0",
        //             "bonus" => "0",
        //             "crossedUnrealizedPL" => "0",
        //             "isolatedUnrealizedPL" => ""
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        return $this->parse_margin_mode($data, $market);
    }

    public function parse_margin_mode(array $marginMode, $market = null): array {
        $marginType = $this->safe_string_lower($marginMode, 'marginMode');
        return array(
            'info' => $marginMode,
            'symbol' => $this->safe_symbol(null, $market),
            'marginMode' => $this->parse_margin_mode_type($marginType),
        );
    }

    public function parse_margin_mode_type(string $type): string {
        $types = array(
            'crossed' => 'cross',
            'fixed' => 'isolated',
        );
        return $this->safe_string($types, $type, $type);
    }

    public function set_margin_mode(string $marginMode, ?string $symbol = null, $params = array ()) {
        /**
         * set margin mode to 'cross' or 'isolated'
         *
         * @see https://coincatch.github.io/github.io/en/mix/#change-margin-mode
         *
         * @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);
        $this->load_markets();
        $market = $this->market($symbol);
        if ($market['type'] !== 'swap') {
            throw new NotSupported($this->id . ' setMarginMode() is not supported for ' . $market['type'] . ' type of markets');
        }
        $request = array(
            'symbol' => $market['id'],
            'marginCoin' => $market['settleId'],
            'marginMode' => $this->encode_margin_mode_type($marginMode),
        );
        $response = $this->privatePostApiMixV1AccountSetMarginMode ($this->extend($request, $params));
        //
        //     {
        //         "code" => "00000",
        //         "msg" => "success",
        //         "requestTime" => 1726670096099,
        //         "data" => {
        //             "symbol" => "ETHUSD_DMCBL",
        //             "marginCoin" => "ETH",
        //             "longLeverage" => 10,
        //             "shortLeverage" => 10,
        //             "crossMarginLeverage" => null,
        //             "marginMode" => "fixed"
        //         }
        //     }
        //
        return $response;
    }

    public function encode_margin_mode_type(string $type): string {
        $types = array(
            'cross' => 'crossed',
            'isolated' => 'fixed',
        );
        return $this->safe_string($types, $type, $type);
    }

    public function fetch_position_mode(?string $symbol = null, $params = array ()) {
        /**
         * fetchs the position mode, hedged or one way
         *
         * @see https://coincatch.github.io/github.io/en/mix/#get-single-account
         *
         * @param {string} $symbol unified $symbol of the $market to fetch entry for
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an object detailing whether the $market is in hedged or one-way mode
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchPositionMode() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        if ($market['type'] !== 'swap') {
            throw new NotSupported($this->id . ' fetchPositionMode() is not supported for ' . $market['type'] . ' type of markets');
        }
        $request = array(
            'symbol' => $market['id'],
            'marginCoin' => $market['settleId'],
        );
        $response = $this->privateGetApiMixV1AccountAccount ($this->extend($request, $params)); // same endpoint
        $data = $this->safe_dict($response, 'data', array());
        $holdMode = $this->safe_string($data, 'holdMode');
        return array(
            'info' => $response,
            'hedged' => $holdMode === 'double_hold',
        );
    }

    public function set_position_mode(bool $hedged, ?string $symbol = null, $params = array ()) {
        /**
         * set $hedged to true or false for a $market
         *
         * @see https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Set%20Position%20Mode
         *
         * @param {bool} $hedged set to true to use dualSidePosition
         * @param {string} $symbol unified $symbol of the $market to fetch entry for
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->productType] 'umcbl' or 'dmcbl' (default 'umcbl' if $symbol is not provided)
         * @return {array} response from the exchange
         */
        $methodName = 'setPositionMode';
        $defaultProductType = 'umcbl';
        $this->load_markets();
        $productType = $this->safe_string($params, 'productType');
        if ($productType === null) {
            if ($symbol !== null) {
                $market = $this->market($symbol);
                if ($market['type'] !== 'swap') {
                    throw new NotSupported($this->id . ' setPositionMode() is not supported for ' . $market['type'] . ' type of markets');
                }
                $marketId = $market['id'];
                $parts = explode('_', $marketId);
                $productType = $this->safe_string_lower($parts, 1, $productType);
            } else {
                $productType = $this->handle_option($methodName, 'productType', $defaultProductType);
            }
        }
        $request = array(
            'productType' => $productType,
            'holdMode' => $hedged ? 'double_hold' : 'single_hold',
        );
        //
        //     {
        //         "code" => "00000",
        //         "msg" => "success",
        //         "requestTime" => 1726677135005,
        //         "data" => {
        //             "marginCoin" => "ETH",
        //             "dualSidePosition" => false
        //         }
        //     }
        //
        return $this->privatePostApiMixV1AccountSetPositionMode ($this->extend($request, $params));
    }

    public function fetch_leverage(string $symbol, $params = array ()): array {
        /**
         * fetch the set leverage for a $market
         *
         * @see https://coincatch.github.io/github.io/en/mix/#get-single-account
         *
         * @param {string} $symbol unified $market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=leverage-structure leverage structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        if ($market['type'] !== 'swap') {
            throw new NotSupported($this->id . ' fetchLeverage() is not supported for ' . $market['type'] . ' type of markets');
        }
        $request = array(
            'symbol' => $market['id'],
            'marginCoin' => $market['settleId'],
        );
        $response = $this->privateGetApiMixV1AccountAccount ($this->extend($request, $params)); // same endpoint
        $data = $this->safe_dict($response, 'data', array());
        return $this->parse_leverage($data, $market);
    }

    public function set_leverage(int $leverage, ?string $symbol = null, $params = array ()) {
        /**
         * set the level of $leverage for a $market
         *
         * @see https://hashkeyglobal-apidoc.readme.io/reference/change-futures-$leverage-trade
         *
         * @param {float} $leverage the rate of $leverage
         * @param {string} $symbol unified $market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->side] *for isolated margin mode with hedged position mode only* 'long' or 'short'
         * @return {array} $response from the exchange
         */
        $methodName = 'setLeverage';
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' ' . $methodName . '() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        if ($market['type'] !== 'swap') {
            throw new NotSupported($this->id . ' ' . $methodName . '() is not supported for ' . $market['type'] . ' type of markets');
        }
        $request = array(
            'symbol' => $market['id'],
            'marginCoin' => $market['settleId'],
            'leverage' => $leverage,
        );
        $side = null;
        list($side, $params) = $this->handle_option_and_params($params, $methodName, 'side');
        if ($side !== null) {
            $request['holdSide'] = $side;
        }
        $response = $this->privatePostApiMixV1AccountSetLeverage ($this->extend($request, $params));
        //
        //     {
        //         "code" => "00000",
        //         "msg" => "success",
        //         "requestTime" => 1726680486657,
        //         "data" => {
        //             "symbol" => "ETHUSD_DMCBL",
        //             "marginCoin" => "ETH",
        //             "longLeverage" => 2,
        //             "shortLeverage" => 2,
        //             "crossMarginLeverage" => 2,
        //             "marginMode" => "crossed"
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        return $this->parse_leverage($data, $market);
    }

    public function parse_leverage(array $leverage, ?array $market = null): array {
        //
        // fetchLeverage
        //     {
        //         "marginCoin" => "ETH",
        //         "locked" => "0",
        //         "available" => "0.01",
        //         "crossMaxAvailable" => "0.01",
        //         "fixedMaxAvailable" => "0.01",
        //         "maxTransferOut" => "0.01",
        //         "equity" => "0.01",
        //         "usdtEquity" => "22.97657025",
        //         "btcEquity" => "0.000386195288",
        //         "crossRiskRate" => "0",
        //         "crossMarginLeverage" => 100,
        //         "fixedLongLeverage" => 100,
        //         "fixedShortLeverage" => 100,
        //         "marginMode" => "crossed",
        //         "holdMode" => "double_hold",
        //         "unrealizedPL" => "0",
        //         "bonus" => "0",
        //         "crossedUnrealizedPL" => "0",
        //         "isolatedUnrealizedPL" => ""
        //     }
        //
        // setLeverage
        //     {
        //         "symbol" => "ETHUSD_DMCBL",
        //         "marginCoin" => "ETH",
        //         "longLeverage" => 2,
        //         "shortLeverage" => 2,
        //         "crossMarginLeverage" => 2,
        //         "marginMode" => "crossed"
        //     }
        //
        $marketId = $this->safe_string($leverage, 'symbol');
        $market = $this->safe_market_custom($marketId, $market);
        $marginMode = $this->parse_margin_mode_type($this->safe_string_lower($leverage, 'marginMode'));
        $longLeverage = $this->safe_integer_2($leverage, 'fixedLongLeverage', 'longLeverage');
        $shortLeverage = $this->safe_integer_2($leverage, 'fixedShortLeverage', 'shortLeverage');
        $crossMarginLeverage = $this->safe_integer($leverage, 'crossMarginLeverage');
        if ($marginMode === 'cross') {
            $longLeverage = $crossMarginLeverage;
            $shortLeverage = $crossMarginLeverage;
        }
        return array(
            'info' => $leverage,
            'symbol' => $market['symbol'],
            'marginMode' => $marginMode,
            'longLeverage' => $longLeverage,
            'shortLeverage' => $shortLeverage,
        );
    }

    public function modify_margin_helper(string $symbol, $amount, $type, $params = array ()): array {
        $methodName = 'modifyMarginHelper';
        list($methodName, $params) = $this->handle_param_string($params, 'methodName', $methodName);
        $this->load_markets();
        $market = $this->market($symbol);
        if ($market['type'] !== 'swap') {
            throw new NotSupported($this->id . ' ' . $methodName . '() is not supported for ' . $market['type'] . ' $type of markets');
        }
        $amount = $this->amount_to_precision($symbol, $amount);
        $request = array(
            'symbol' => $market['id'],
            'marginCoin' => $market['settleId'],
            'amount' => $amount, // positive value for adding margin, negative for reducing
        );
        $side = null;
        list($side, $params) = $this->handle_option_and_params($params, $methodName, 'side');
        if ($side !== null) {
            $request['holdSide'] = $side;
        }
        $response = $this->privatePostApiMixV1AccountSetMargin ($this->extend($request, $params));
        // todo check $response
        // always returns error
        // addMargin - "code":"45006","msg":"Insufficient position","requestTime":1729162281543,"data":null
        // reduceMargin - "code":"40800","msg":"Insufficient $amount of margin","requestTime":1729162362718,"data":null
        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 {
        //
        //
        $msg = $this->safe_string($data, 'msg');
        $status = ($msg === 'success') ? 'ok' : 'failed';
        return array(
            'info' => $data,
            'symbol' => $market['symbol'],
            'type' => null,
            'marginMode' => null,
            '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
         *
         * @see https://coincatch.github.io/github.io/en/mix/#change-margin
         *
         * @param {string} $symbol unified market $symbol
         * @param {float} $amount the $amount of margin to remove
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->side] *for isolated margin mode with hedged position mode only* 'long' or 'short'
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=reduce-margin-structure margin structure~
         */
        $params['methodName'] = 'reduceMargin';
        return $this->modify_margin_helper($symbol, -$amount, 'reduce', $params);
    }

    public function add_margin(string $symbol, float $amount, $params = array ()): array {
        /**
         * add margin
         *
         * @see https://coincatch.github.io/github.io/en/mix/#change-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
         * @param {string} [$params->side] *for isolated margin mode with hedged position mode only* 'long' or 'short'
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=add-margin-structure margin structure~
         */
        $params['methodName'] = 'addMargin';
        return $this->modify_margin_helper($symbol, $amount, 'add', $params);
    }

    public function fetch_position(string $symbol, $params = array ()): array {
        /**
         * fetch data on a single open contract trade $position
         *
         * @see https://coincatch.github.io/github.io/en/mix/#get-$symbol-$position
         *
         * @param {string} $symbol unified market $symbol of the market the $position is held in, default is null
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string}  [$params->side] 'long' or 'short' *for non-hedged $position mode only* (default 'long')
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=$position-structure $position structure~
         */
        $methodName = 'fetchPosition';
        $side = 'long';
        list($side, $params) = $this->handle_option_and_params($params, $methodName, 'side');
        $positions = $this->fetch_positions_for_symbol($symbol, $params);
        $arrayLength = count($positions);
        if ($arrayLength > 1) {
            for ($i = 0; $i < count($positions); $i++) {
                $position = $positions[$i];
                if ($position['side'] === $side) {
                    return $position;
                }
            }
        }
        return $this->safe_dict($positions, 0, array());
    }

    public function fetch_positions_for_symbol(string $symbol, $params = array ()): array {
        /**
         * fetch open positions for a single $market
         *
         * @see https://coincatch.github.io/github.io/en/mix/#get-$symbol-position
         *
         * fetch all open positions for specific $symbol
         * @param {string} $symbol unified $market $symbol
         * @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();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            'marginCoin' => $market['settleId'],
        );
        $response = $this->privateGetApiMixV1PositionSinglePositionV2 ($this->extend($request, $params));
        //
        //     {
        //         "code" => "00000",
        //         "msg" => "success",
        //         "requestTime" => 1726926959041,
        //         "data" => array(
        //             {
        //                 "marginCoin" => "USDT",
        //                 "symbol" => "ETHUSDT_UMCBL",
        //                 "holdSide" => "long",
        //                 "openDelegateCount" => "0",
        //                 "margin" => "2.55512",
        //                 "available" => "0.01",
        //                 "locked" => "0",
        //                 "total" => "0.01",
        //                 "leverage" => 10,
        //                 "achievedProfits" => "0",
        //                 "averageOpenPrice" => "2555.12",
        //                 "marginMode" => "crossed",
        //                 "holdMode" => "double_hold",
        //                 "unrealizedPL" => "0.1371",
        //                 "liquidationPrice" => "-3433.328491",
        //                 "keepMarginRate" => "0.0033",
        //                 "marketPrice" => "2568.83",
        //                 "marginRatio" => "0.001666357648",
        //                 "autoMargin" => "off",
        //                 "cTime" => "1726919819686"
        //             }
        //         )
        //     }
        //
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_positions($data, array( $symbol ));
    }

    public function fetch_positions(?array $symbols = null, $params = array ()): array {
        /**
         * fetch all open positions
         *
         * @see https://coincatch.github.io/github.io/en/mix/#get-all-position
         *
         * @param {string[]} [$symbols] list of unified market $symbols (all $symbols must belong to the same product type)
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->productType] 'umcbl' or 'dmcbl' (default 'umcbl' if $symbols are not provided)
         * @param {string} [$params->marginCoin] the settle $currency of the positions, needs to match the $productType
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=position-structure position structure~
         */
        $methodName = 'fetchPositions';
        $this->load_markets();
        $productType = 'umcbl';
        if ($symbols !== null) {
            $marketIds = $this->market_ids($symbols);
            $productTypes = array();
            for ($i = 0; $i < count($marketIds); $i++) {
                $marketId = $marketIds[$i];
                $parts = explode('_', $marketId);
                $marketProductType = $this->safe_string($parts, 1);
                $productTypes[] = $marketProductType;
            }
            $productTypes = $this->unique($productTypes);
            $arrayLength = count($productTypes);
            if ($arrayLength > 1) {
                throw new BadSymbol($this->id . ' ' . $methodName . '() requires all $symbols to belong to the same product type (umcbl or dmcbl)');
            } else {
                $productType = $productTypes[0];
            }
        } else {
            list($productType, $params) = $this->handle_option_and_params($params, $methodName, 'productType', $productType);
        }
        $request = array(
            'productType' => $productType,
        );
        if ($productType === 'dmcbl') {
            $marginCoin = null;
            list($marginCoin, $params) = $this->handle_option_and_params($params, $methodName, 'marginCoin');
            if ($marginCoin !== null) {
                $currency = $this->currency($marginCoin);
                $request['marginCoin'] = $currency['id'];
            }
        }
        $response = $this->privateGetApiMixV1PositionAllPositionV2 ($this->extend($request, $params));
        //
        //     {
        //         "code" => "00000",
        //         "msg" => "success",
        //         "requestTime" => 1726933132054,
        //         "data" => array(
        //             {
        //                 "marginCoin" => "USDT",
        //                 "symbol" => "ETHUSDT_UMCBL",
        //                 "holdSide" => "long",
        //                 "openDelegateCount" => "0",
        //                 "margin" => "2.55512",
        //                 "available" => "0.01",
        //                 "locked" => "0",
        //                 "total" => "0.01",
        //                 "leverage" => 10,
        //                 "achievedProfits" => "0",
        //                 "averageOpenPrice" => "2555.12",
        //                 "marginMode" => "crossed",
        //                 "holdMode" => "double_hold",
        //                 "unrealizedPL" => "0.0093",
        //                 "liquidationPrice" => "-3433.378333",
        //                 "keepMarginRate" => "0.0033",
        //                 "marketPrice" => "2556.05",
        //                 "marginRatio" => "0.001661599511",
        //                 "autoMargin" => "off",
        //                 "cTime" => "1726919819686",
        //                 "uTime" => "1726919819686"
        //             }
        //         )
        //     }
        //
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_positions($data, $symbols);
    }

    public function parse_position(array $position, ?array $market = null) {
        //
        //     {
        //         "marginCoin" => "USDT",
        //         "symbol" => "ETHUSDT_UMCBL",
        //         "holdSide" => "long",
        //         "openDelegateCount" => "0",
        //         "margin" => "2.55512",
        //         "available" => "0.01",
        //         "locked" => "0",
        //         "total" => "0.01",
        //         "leverage" => 10,
        //         "achievedProfits" => "0",
        //         "averageOpenPrice" => "2555.12",
        //         "marginMode" => "crossed",
        //         "holdMode" => "double_hold",
        //         "unrealizedPL" => "0.0093",
        //         "liquidationPrice" => "-3433.378333",
        //         "keepMarginRate" => "0.0033",
        //         "marketPrice" => "2556.05",
        //         "marginRatio" => "0.001661599511",
        //         "autoMargin" => "off",
        //         "cTime" => "1726919819686",
        //         "uTime" => "1726919819686"
        //     }
        //
        $marketId = $this->safe_string($position, 'symbol');
        $settleId = $this->safe_string($position, 'marginCoin');
        $market = $this->safe_market_custom($marketId, $market, $settleId);
        $timestamp = $this->safe_integer($position, 'cTime');
        $marginMode = $this->safe_string($position, 'marginMode');
        $isHedged = null;
        $holdMode = $this->safe_string($position, 'holdMode');
        if ($holdMode === 'double_hold') {
            $isHedged = true;
        } elseif ($holdMode === 'single_hold') {
            $isHedged = false;
        }
        $margin = $this->safe_number($position, 'margin');
        $keepMarginRate = $this->safe_string($position, 'keepMarginRate');
        return $this->safe_position(array(
            'symbol' => $market['symbol'],
            'id' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'contracts' => $this->safe_number($position, 'total'), // todo check
            'contractSize' => null,
            'side' => $this->safe_string_lower($position, 'holdSide'),
            'notional' => $margin, // todo check
            'leverage' => $this->safe_integer($position, 'leverage'),
            'unrealizedPnl' => $this->safe_number($position, 'unrealizedPL'),
            'realizedPnl' => $this->safe_number($position, 'achievedProfits'),
            'collateral' => null, // todo check
            'entryPrice' => $this->safe_number($position, 'averageOpenPrice'),
            'markPrice' => $this->safe_number($position, 'marketPrice'),
            'liquidationPrice' => $this->safe_number($position, 'liquidationPrice'),
            'marginMode' => $this->parse_margin_mode_type($marginMode),
            'hedged' => $isHedged,
            'maintenanceMargin' => null, // todo check
            'maintenanceMarginPercentage' => $this->parse_number(Precise::string_mul($keepMarginRate, '100')), // todo check
            'initialMargin' => $margin, // todo check
            'initialMarginPercentage' => null,
            'marginRatio' => $this->safe_number($position, 'marginRatio'),
            'lastUpdateTimestamp' => $this->safe_integer($position, 'uTime'),
            'lastPrice' => null,
            'stopLossPrice' => null,
            'takeProfitPrice' => null,
            'percentage' => null,
            'info' => $position,
        ));
    }

    public function safe_market_custom(?string $marketId, ?array $market = null, ?string $settleId = null): array {
        try {
            $market = $this->safe_market($marketId, $market);
        } catch (Exception $e) {
            // dmcbl markets have the same id and $market type but different $settleId
            // so we need to resolve the $market by $settleId
            $marketsWithCurrentId = $this->safe_list($this->markets_by_id, $marketId, array());
            if ($settleId === null) {
                $market = $marketsWithCurrentId[0]; // if $settleId is not provided, return the first $market with the current id
            } else {
                for ($i = 0; $i < count($marketsWithCurrentId); $i++) {
                    $marketWithCurrentId = $marketsWithCurrentId[$i];
                    if ($marketWithCurrentId['settleId'] === $settleId) {
                        $market = $marketWithCurrentId;
                        break;
                    }
                }
            }
        }
        return $market;
    }

    public function fetch_ledger(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch the history of changes, actions done by the user or operations that altered balance of the user
         *
         * @see https://coincatch.github.io/github.io/en/spot/#get-bills
         * @see https://coincatch.github.io/github.io/en/mix/#get-business-account-bill
         *
         * @param {string} [$code] unified $currency $code
         * @param {int} [$since] timestamp in ms of the earliest ledger entry, default is null
         * @param {int} [$limit] max number of ledger entrys to return, default is null
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] *swap only* the latest time in ms to fetch entries for
         * @param {string} [$params->type] 'spot' or 'swap' (default 'spot')
         * @param {string} [$params->after] *spot only* billId, return the $data less than this billId
         * @param {string} [$params->before] *spot only* billId, return the $data greater than or equals to this billId
         * @param {string} [$params->groupType] *spot only*
         * @param {string} [$params->bizType] *spot only*
         * @param {string} [$params->productType] *swap only* 'umcbl' or 'dmcbl' (default 'umcbl' or 'dmcbl' if $code is provided and $code is not equal to 'USDT')
         * @param {string} [$params->business] *swap only*
         * @param {string} [$params->lastEndId] *swap only*
         * @param {bool} [$params->next] *swap only*
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=ledger ledger structure~
         */
        $methodName = 'fetchLedger';
        $this->load_markets();
        $request = array();
        $marketType = 'spot';
        list($marketType, $params) = $this->handle_market_type_and_params($methodName, null, $params, $marketType);
        $result = null;
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
        }
        if ($marketType === 'spot') {
            if ($currency !== null) {
                $numericId = $this->safe_string($currency, 'numericId');
                $request['coinId'] = $numericId;
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $response = $this->privatePostApiSpotV1AccountBills ($this->extend($request, $params));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1727964749515,
            //         "data" => array(
            //             array(
            //                 "billId" => "1220289012519190529",
            //                 "coinId" => 2,
            //                 "coinName" => "USDT",
            //                 "groupType" => "transfer",
            //                 "bizType" => "Transfer out",
            //                 "quantity" => "-40.00000000",
            //                 "balance" => "4.43878673",
            //                 "fees" => "0.00000000",
            //                 "cTime" => "1726665493092"
            //             ),
            //             ...
            //         )
            //     }
            //
            $result = $this->safe_list($response, 'data', array());
        } elseif ($marketType === 'swap') {
            if ($since !== null) {
                $request['startTime'] = $since;
            } else {
                $request['startTime'] = 0; // is mandatory
            }
            $until = null;
            list($until, $params) = $this->handle_option_and_params($params, $methodName, 'until');
            if ($until !== null) {
                $request['endTime'] = $until;
            } else {
                $request['endTime'] = $this->milliseconds(); // is mandatory
            }
            if ($limit !== null) {
                $request['pageSize'] = $limit;
            }
            $productType = 'umcbl';
            if ($code === null) {
                $productType = $this->handle_option($methodName, 'productType', $productType);
            } elseif ($code === 'USDT') {
                $productType = 'umcbl';
            } else {
                $productType = 'dmcbl';
            }
            list($productType, $params) = $this->handle_param_string($params, 'productType', $productType);
            $request['productType'] = $productType;
            $response = $this->privateGetApiMixV1AccountAccountBusinessBill ($this->extend($request, $params));
            //
            //     {
            //         "code" => "00000",
            //         "msg" => "success",
            //         "requestTime" => 1727971607663,
            //         "data" => {
            //             "result" => array(
            //                 array(
            //                     "id" => "1225766556446064640",
            //                     "symbol" => null,
            //                     "marginCoin" => "ETH",
            //                     "amount" => "-0.0016",
            //                     "fee" => "0",
            //                     "feeByCoupon" => "",
            //                     "feeCoin" => "ETH",
            //                     "business" => "trans_to_exchange",
            //                     "cTime" => "1727971441425"
            //                 ),
            //                 array(
            //                     "id" => "1225467081664061441",
            //                     "symbol" => "ETHUSD_DMCBL",
            //                     "marginCoin" => "ETH",
            //                     "amount" => "-0.00052885",
            //                     "fee" => "0",
            //                     "feeByCoupon" => "",
            //                     "feeCoin" => "ETH",
            //                     "business" => "risk_captital_user_transfer",
            //                     "cTime" => "1727900041024"
            //                 ),
            //                 array(
            //                     "id" => "1225467075440189441",
            //                     "symbol" => "ETHUSD_DMCBL",
            //                     "marginCoin" => "ETH",
            //                     "amount" => "-0.0083359",
            //                     "fee" => "-0.00005996",
            //                     "feeByCoupon" => "",
            //                     "feeCoin" => "ETH",
            //                     "business" => "burst_long_loss_query",
            //                     "cTime" => "1727900039576"
            //                 ),
            //                 array(
            //                     "id" => "1221416895715303426",
            //                     "symbol" => "ETHUSD_DMCBL",
            //                     "marginCoin" => "ETH",
            //                     "amount" => "0.00004756",
            //                     "fee" => "0",
            //                     "feeByCoupon" => "",
            //                     "feeCoin" => "ETH",
            //                     "business" => "contract_settle_fee",
            //                     "cTime" => "1726934401444"
            //                 ),
            //                 array(
            //                     "id" => "1221413703233871873",
            //                     "symbol" => "ETHUSD_DMCBL",
            //                     "marginCoin" => "ETH",
            //                     "amount" => "0",
            //                     "fee" => "-0.00005996",
            //                     "feeByCoupon" => "",
            //                     "feeCoin" => "ETH",
            //                     "business" => "open_long",
            //                     "cTime" => "1726933640336"
            //                 ),
            //                 {
            //                     "id" => "1220288640761122816",
            //                     "symbol" => null,
            //                     "marginCoin" => "ETH",
            //                     "amount" => "0.01",
            //                     "fee" => "0",
            //                     "feeByCoupon" => "",
            //                     "feeCoin" => "ETH",
            //                     "business" => "trans_from_exchange",
            //                     "cTime" => "1726665404563"
            //                 }
            //             ),
            //             "lastEndId" => "1220288641021337600",
            //             "nextFlag" => false,
            //             "preFlag" => false
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $result = $this->safe_list($data, 'result', array());
        } else {
            throw new NotSupported($this->id . ' ' . $methodName . '() does not support market type ' . $marketType);
        }
        return $this->parse_ledger($result, $currency, $since, $limit);
    }

    public function parse_ledger_entry(array $item, ?array $currency = null): array {
        //
        // spot
        //     {
        //         "billId" => "1220289012519190529",
        //         "coinId" => 2,
        //         "coinName" => "USDT",
        //         "groupType" => "transfer",
        //         "bizType" => "Transfer out",
        //         "quantity" => "-40.00000000",
        //         "balance" => "4.43878673",
        //         "fees" => "0.00000000",
        //         "cTime" => "1726665493092"
        //     }
        //
        // swap
        //     {
        //         "id" => "1220288640761122816",
        //         "symbol" => null,
        //         "marginCoin" => "ETH",
        //         "amount" => "0.01",
        //         "fee" => "0",
        //         "feeByCoupon" => "",
        //         "feeCoin" => "ETH",
        //         "business" => "trans_from_exchange",
        //         "cTime" => "1726665404563"
        //     }
        //
        $timestamp = $this->safe_integer($item, 'cTime');
        $settleId = $this->safe_string_2($item, 'coinName', 'marginCoin');
        $market = null;
        $marketId = $this->safe_string($item, 'symbol');
        $market = $this->safe_market_custom($marketId, $market, $settleId);
        $amountString = $this->safe_string_2($item, 'quantity', 'amount');
        $direction = 'in';
        if (Precise::string_lt($amountString, '0')) {
            $direction = 'out';
            $amountString = Precise::string_mul($amountString, '-1');
        }
        $fee = array(
            'cost' => Precise::string_abs($this->safe_string_2($item, 'fee', 'fees')),
            'currency' => $this->safe_string($item, 'feeCoin'),
        );
        return $this->safe_ledger_entry(array(
            'id' => $this->safe_string_2($item, 'billId', 'id'),
            'info' => $item,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'account' => null,
            'direction' => $direction,
            'referenceId' => null,
            'referenceAccount' => null,
            'type' => $this->parse_ledger_entry_type($this->safe_string_lower_2($item, 'bizType', 'business')),
            'currency' => $this->safe_currency_code($settleId, $currency),
            'symbol' => $market['symbol'],
            'amount' => $amountString,
            'before' => null,
            'after' => $this->safe_string($item, 'balance'),
            'status' => 'ok',
            'fee' => $fee,
        ), $currency);
    }

    public function parse_ledger_entry_type(string $type): string {
        $types = array(
            'deposit' => 'deposit',
            'withdraw' => 'withdrawal',
            'buy' => 'trade',
            'sell' => 'trade',
            'deduction of handling fee' => 'fee', // todo check
            'transfer-in' => 'transfer',
            'transfer in' => 'transfer',
            'transfer out' => 'transfer',
            'rebate rewards' => 'rebate', // todo check
            'airdrop rewards' => 'rebate', // todo check
            'usdt contract rewards' => 'rebate', // todo check
            'mix contract rewards' => 'rebate', // todo check
            'system lock' => 'system lock',
            'user lock' => 'user lock',
            'open_long' => 'trade',
            'open_short' => 'trade',
            'close_long' => 'trade',
            'close_short' => 'trade',
            'trans_from_exchange' => 'transfer',
            'trans_to_exchange' => 'transfer',
            'contract_settle_fee' => 'fee', // todo check sometimes it is positive, sometimes negative
            'burst_long_loss_query' => 'trade', // todo check
            'burst_short_loss_query' => 'trade', // todo check
        );
        return $this->safe_string($types, $type, $type);
    }

    public function handle_errors(int $code, string $reason, string $url, string $method, array $headers, string $body, $response, $requestHeaders, $requestBody) {
        if (!$response) {
            return null; // fallback to default error handler
        }
        $message = $this->safe_string($response, 'msg');
        $feedback = $this->id . ' ' . $body;
        $messageCode = $this->safe_string($response, 'code');
        $success = ($message === 'success') || ($message === null);
        if (mb_strpos($url, 'batch') !== false) { // createOrders, cancelOrders
            $data = $this->safe_dict($response, 'data', array());
            $failure = $this->safe_list_2($data, 'failure', 'fail_infos', array());
            if (!$this->is_empty($failure)) {
                $success = false;
                $firstEntry = $this->safe_dict($failure, 0, array());
                $messageCode = $this->safe_string($firstEntry, 'errorCode');
                $message = $this->safe_string($firstEntry, 'errorMsg');
            }
        }
        if (!$success) {
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $messageCode, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $message, $feedback);
            throw new ExchangeError($feedback); // unknown $message
        }
        return null;
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $endpoint = '/' . $path;
        if ($method === 'GET') {
            $query = $this->urlencode($params);
            if (strlen($query) !== 0) {
                $endpoint .= '?' . $query;
            }
        }
        if ($api === 'private') {
            $this->check_required_credentials();
            $timestamp = $this->number_to_string($this->milliseconds());
            $suffix = '';
            if ($method !== 'GET') {
                $body = $this->json($params);
                $suffix = $body;
            }
            $payload = $timestamp . $method . $endpoint . $suffix;
            $signature = $this->hmac($this->encode($payload), $this->encode($this->secret), 'sha256', 'base64');
            $headers = array(
                'ACCESS-KEY' => $this->apiKey,
                'ACCESS-SIGN' => $signature,
                'ACCESS-TIMESTAMP' => $timestamp,
                'ACCESS-PASSPHRASE' => $this->password,
                'Content-Type' => 'application/json',
                'X-CHANNEL-API-CODE' => $this->safe_string($this->options, 'brokerId', '47cfy'),
            );
        }
        $url = $this->urls['api'][$api] . $endpoint;
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }
}
