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

class backpack extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'backpack',
            'name' => 'Backpack',
            'countries' => array( 'JP' ), // Japan
            'rateLimit' => 50, // 20 times per second
            'version' => 'v1',
            'certified' => false,
            'pro' => true,
            'has' => array(
                'CORS' => null,
                'spot' => true,
                'margin' => true,
                'swap' => true,
                'future' => false,
                'option' => false,
                'addMargin' => false,
                'cancelAllOrders' => true,
                'cancelAllOrdersAfter' => false,
                'cancelOrder' => true,
                'cancelOrders' => false,
                'cancelWithdraw' => false,
                'closePosition' => false,
                'createConvertTrade' => false, // todo
                'createDepositAddress' => false,
                'createLimitBuyOrder' => true,
                'createLimitOrder' => true,
                'createLimitSellOrder' => true,
                'createMarketBuyOrder' => true,
                'createMarketBuyOrderWithCost' => true,
                'createMarketOrder' => true,
                'createMarketOrderWithCost' => true,
                'createMarketSellOrder' => true,
                'createMarketSellOrderWithCost' => true,
                'createOrder' => true,
                'createOrders' => true,
                'createOrderWithTakeProfitAndStopLoss' => true,
                'createPostOnlyOrder' => true,
                'createReduceOnlyOrder' => true,
                'createStopLossOrder' => false,
                'createTakeProfitOrder' => false,
                'createTrailingAmountOrder' => false,
                'createTrailingPercentOrder' => false,
                'createTriggerOrder' => true,
                'fetchAccounts' => false,
                'fetchBalance' => true,
                'fetchCanceledAndClosedOrders' => false,
                'fetchCanceledOrders' => false,
                'fetchClosedOrder' => false,
                'fetchClosedOrders' => false,
                'fetchConvertCurrencies' => false,
                'fetchConvertQuote' => false,
                'fetchConvertTrade' => false,
                'fetchConvertTradeHistory' => false,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => true,
                'fetchDeposits' => true,
                'fetchDepositsWithdrawals' => false,
                'fetchDepositWithdrawFees' => false,
                'fetchFundingHistory' => true,
                'fetchFundingRate' => true,
                'fetchFundingRateHistory' => true,
                'fetchFundingRates' => false,
                'fetchIndexOHLCV' => true,
                'fetchLedger' => false,
                'fetchLeverage' => false,
                'fetchLeverageTiers' => false,
                'fetchMarginAdjustmentHistory' => false,
                'fetchMarginMode' => false,
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => true,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenInterest' => true,
                'fetchOpenInterestHistory' => true,
                'fetchOpenOrder' => true,
                'fetchOpenOrders' => true,
                'fetchOrder' => false,
                'fetchOrderBook' => true,
                'fetchOrders' => true,
                'fetchOrderTrades' => false,
                'fetchPosition' => false,
                'fetchPositionHistory' => false,
                'fetchPositionMode' => false,
                'fetchPositions' => true,
                'fetchPositionsForSymbol' => false,
                'fetchPositionsHistory' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchStatus' => true,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => false,
                'fetchTradingFees' => false,
                'fetchTransactions' => false,
                'fetchTransfers' => false,
                'fetchWithdrawals' => true,
                'reduceMargin' => false,
                'sandbox' => false,
                'setLeverage' => false,
                'setMargin' => false,
                'setMarginMode' => false,
                'setPositionMode' => false,
                'transfer' => false,
                'withdraw' => true,
            ),
            'timeframes' => array(
                '1m' => '1m',
                '3m' => '3m',
                '5m' => '5m',
                '15' => '15m',
                '30' => '30m',
                '1h' => '1h',
                '2h' => '2h',
                '4h' => '4h',
                '6h' => '6h',
                '8h' => '8h',
                '12h' => '12h',
                '1d' => '1d',
                '3d' => '3d',
                '1w' => '1w',
                '1M' => '1month',
            ),
            'urls' => array(
                'logo' => 'https://github.com/user-attachments/assets/cc04c278-679f-4554-9f72-930dd632b80f',
                'api' => array(
                    'public' => 'https://api.backpack.exchange',
                    'private' => 'https://api.backpack.exchange',
                ),
                'www' => 'https://backpack.exchange/',
                'doc' => 'https://docs.backpack.exchange/',
                'referral' => 'https://backpack.exchange/join/ccxt',
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'api/v1/assets' => 1, // done
                        'api/v1/collateral' => 1, // not used
                        'api/v1/borrowLend/markets' => 1,
                        'api/v1/borrowLend/markets/history' => 1,
                        'api/v1/markets' => 1, // done
                        'api/v1/market' => 1, // not used
                        'api/v1/ticker' => 1, // done
                        'api/v1/tickers' => 1, // done
                        'api/v1/depth' => 1, // done
                        'api/v1/klines' => 1, // done
                        'api/v1/markPrices' => 1, // done
                        'api/v1/openInterest' => 1, // done
                        'api/v1/fundingRates' => 1, // done
                        'api/v1/status' => 1, // done
                        'api/v1/ping' => 1, // todo check if it is needed for ws
                        'api/v1/time' => 1, // done
                        'api/v1/wallets' => 1, // not used
                        'api/v1/trades' => 1, // done
                        'api/v1/trades/history' => 1, // done
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'api/v1/account' => 1, // todo fetchTradingFee
                        'api/v1/account/limits/borrow' => 1, // not used
                        'api/v1/account/limits/order' => 1, // not used
                        'api/v1/account/limits/withdrawal' => 1, // not used
                        'api/v1/borrowLend/positions' => 1, // todo fetchBorrowInterest
                        'api/v1/capital' => 1, // done
                        'api/v1/capital/collateral' => 1, // not used
                        'wapi/v1/capital/deposits' => 1, // done
                        'wapi/v1/capital/deposit/address' => 1, // done
                        'wapi/v1/capital/withdrawals' => 1, // todo complete after withdrawal
                        'api/v1/position' => 1, // done but todo check if all is right
                        'wapi/v1/history/borrowLend' => 1, // not used
                        'wapi/v1/history/interest' => 1, // not used
                        'wapi/v1/history/borrowLend/positions' => 1, // not used
                        'wapi/v1/history/dust' => 1, // not used
                        'wapi/v1/history/fills' => 1, // done
                        'wapi/v1/history/funding' => 1, // done
                        'wapi/v1/history/orders' => 1, // done
                        'wapi/v1/history/rfq' => 1,
                        'wapi/v1/history/quote' => 1,
                        'wapi/v1/history/settlement' => 1,
                        'wapi/v1/history/strategies' => 1,
                        'api/v1/order' => 1, // done
                        'api/v1/orders' => 1, // done
                    ),
                    'post' => array(
                        'api/v1/account/convertDust' => 1,
                        'api/v1/borrowLend' => 1, // todo borrowCrossMargin
                        'wapi/v1/capital/withdrawals' => 1, // todo complete after withdrawal
                        'api/v1/order' => 1, // done
                        'api/v1/orders' => 1, // done
                        'api/v1/rfq' => 1,
                        'api/v1/rfq/accept' => 1,
                        'api/v1/rfq/refresh' => 1,
                        'api/v1/rfq/cancel' => 1,
                        'api/v1/rfq/quote' => 1,
                    ),
                    'delete' => array(
                        'api/v1/order' => 1, // done
                        'api/v1/orders' => 1, // done
                    ),
                    'patch' => array(
                        'api/v1/account' => 1,
                    ),
                ),
            ),
            'features' => array(
                'default' => array(
                    'sandbox' => false,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => true,
                        'triggerPriceType' => null,
                        'triggerDirection' => false,
                        'stopLossPrice' => true,
                        'takeProfitPrice' => true,
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'GTC' => true,
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => true,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'trailing' => false,
                        'leverage' => false,
                        'marketBuyByCost' => true,
                        'marketBuyRequiresPrice' => true,
                        'selfTradePrevention' => array(
                            'EXPIRE_MAKER' => true,
                            'EXPIRE_TAKER' => true,
                            'EXPIRE_BOTH' => true,
                            'NONE' => false,
                        ),
                        'iceberg' => false,
                    ),
                    'createOrders' => array(
                        'max' => 20,
                    ),
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 1000,
                        'daysBack' => null,
                        'untilDays' => null,
                        'symbolRequired' => false,
                    ),
                    'fetchOrder' => null,
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => 1000,
                        'trigger' => true,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => array(
                        'marginMode' => false,
                        'limit' => 1000,
                        'daysBack' => null,
                        'untilDays' => null,
                        'trigger' => true,
                        'trailing' => false,
                        'symbolRequired' => true,
                    ),
                    'fetchClosedOrders' => null,
                    'fetchOHLCV' => array(
                        'paginate' => false,
                        'limit' => 1000,
                    ),
                ),
                'spot' => array(
                    'extends' => 'default',
                ),
                'swap' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
            'requiredCredentials' => array(
                'apiKey' => true,
                'secret' => true,
            ),
            'precisionMode' => TICK_SIZE,
            'options' => array(
                'instructions' => array(
                    'api/v1/account' => array(
                        'GET' => 'accountQuery',
                        'PATCH' => 'accountUpdate',
                    ),
                    'api/v1/capital' => array(
                        'GET' => 'balanceQuery',
                    ),
                    'api/v1/account/limits/borrow' => array(
                        'GET' => 'maxBorrowQuantity',
                    ),
                    'api/v1/account/limits/order' => array(
                        'GET' => 'maxOrderQuantity',
                    ),
                    'api/v1/account/limits/withdrawal' => array(
                        'GET' => 'maxWithdrawalQuantity',
                    ),
                    'api/v1/borrowLend/positions' => array(
                        'GET' => 'borrowLendPositionQuery',
                    ),
                    'api/v1/borrowLend' => array(
                        'POST' => 'borrowLendExecute',
                    ),
                    'wapi/v1/history/borrowLend/positions' => array(
                        'GET' => 'borrowPositionHistoryQueryAll',
                    ),
                    'wapi/v1/history/borrowLend' => array(
                        'GET' => 'borrowHistoryQueryAll',
                    ),
                    'wapi/v1/history/dust' => array(
                        'GET' => 'dustHistoryQueryAll',
                    ),
                    'api/v1/capital/collateral' => array(
                        'GET' => 'collateralQuery',
                    ),
                    'wapi/v1/capital/deposit/address' => array(
                        'GET' => 'depositAddressQuery',
                    ),
                    'wapi/v1/capital/deposits' => array(
                        'GET' => 'depositQueryAll',
                    ),
                    'wapi/v1/history/fills' => array(
                        'GET' => 'fillHistoryQueryAll',
                    ),
                    'wapi/v1/history/funding' => array(
                        'GET' => 'fundingHistoryQueryAll',
                    ),
                    'wapi/v1/history/interest' => array(
                        'GET' => 'interestHistoryQueryAll',
                    ),
                    'api/v1/order' => array(
                        'GET' => 'orderQuery',
                        'POST' => 'orderExecute',
                        'DELETE' => 'orderCancel',
                    ),
                    'api/v1/orders' => array(
                        'GET' => 'orderQueryAll',
                        'POST' => 'orderExecute',
                        'DELETE' => 'orderCancelAll',
                    ),
                    'wapi/v1/history/orders' => array(
                        'GET' => 'orderHistoryQueryAll',
                    ),
                    'wapi/v1/history/pnl' => array(
                        'GET' => 'pnlHistoryQueryAll',
                    ),
                    'wapi/v1/history/rfq' => array(
                        'GET' => 'rfqHistoryQueryAll',
                    ),
                    'wapi/v1/history/quote' => array(
                        'GET' => 'quoteHistoryQueryAll',
                    ),
                    'wapi/v1/history/settlement' => array(
                        'GET' => 'settlementHistoryQueryAll',
                    ),
                    'api/v1/position' => array(
                        'GET' => 'positionQuery',
                    ),
                    'api/v1/rfq/quote' => array(
                        'POST' => 'quoteSubmit',
                    ),
                    'wapi/v1/history/strategies' => array(
                        'GET' => 'strategyHistoryQueryAll',
                    ),
                    'wapi/v1/capital/withdrawals' => array(
                        'GET' => 'withdrawalQueryAll',
                        'POST' => 'withdraw',
                    ),
                ),
                'recvWindow' => 5000, // default is 5000, max is 60000
                'brokerId' => '',
                'currencyIdsListForParseMarket' => null,
                'broker' => '',
                'timeDifference' => 0, // the difference between system clock and the exchange server clock in milliseconds
                'adjustForTimeDifference' => false, // controls the adjustment logic upon instantiation
                'networks' => array(
                    'APT' => 'Aptos',
                    'ARB' => 'Arbitrum',
                    'AVAX' => 'Avalanche',
                    'BASE' => 'Base',
                    'BERA' => 'Berachain',
                    'BTC' => 'Bitcoin',
                    'BCH' => 'BitcoinCash',
                    'BSC' => 'Bsc',
                    'ADA' => 'Cardano',
                    'DOGE' => 'Dogecoin',
                    'ECLIPSE' => 'Eclipse',
                    'EQUALSMONEY' => 'EqualsMoney',
                    'ERC20' => 'Ethereum',
                    'HYP' => 'Hyperliquid',
                    'LTC' => 'Litecoin',
                    'OPTIMISM' => 'Optimism',
                    'MATIC' => 'Polygon',
                    'SEI' => 'Sei',
                    'SUI' => 'Sui',
                    'SOL' => 'Solana',
                    'STORY' => 'Story',
                    'TRC20' => 'Tron',
                    'XRP' => 'XRP',
                ),
                'networksById' => array(
                    'aptos' => 'APT',
                    'arbitrum' => 'ARB',
                    'avalanche' => 'AVAX',
                    'base' => 'BASE',
                    'berachain' => 'BERA',
                    'bitcoin' => 'BTC',
                    'bitcoincash' => 'BCH',
                    'bsc' => 'BSC',
                    'cardano' => 'ADA',
                    'dogecoin' => 'DOGE',
                    'eclipse' => 'ECLIPSE',
                    'equalsmoney' => 'EQUALSMONEY',
                    'ethereum' => 'ERC20',
                    'hyperliquid' => 'HYP',
                    'litecoin' => 'LTC',
                    'optimism' => 'OPTIMISM',
                    'polygon' => 'MATIC',
                    'sei' => 'SEI',
                    'sui' => 'SUI',
                    'solana' => 'SOL',
                    'story' => 'STORY',
                    'tron' => 'TRC20',
                    'xrp' => 'XRP',
                ),
            ),
            'commonCurrencies' => array(),
            'exceptions' => array(
                'exact' => array(
                    'INVALID_CLIENT_REQUEST' => '\\ccxt\\BadRequest',
                    'INVALID_ORDER' => '\\ccxt\\InvalidOrder',
                    'ACCOUNT_LIQUIDATING' => '\\ccxt\\BadRequest',
                    'BORROW_LIMIT' => '\\ccxt\\BadRequest',
                    'BORROW_REQUIRES_LEND_REDEEM' => '\\ccxt\\BadRequest',
                    'FORBIDDEN' => '\\ccxt\\OperationRejected',
                    'INSUFFICIENT_FUNDS' => '\\ccxt\\InsufficientFunds',
                    'INSUFFICIENT_MARGIN' => '\\ccxt\\InsufficientFunds',
                    'INSUFFICIENT_SUPPLY' => '\\ccxt\\InsufficientFunds',
                    'INVALID_ASSET' => '\\ccxt\\BadRequest',
                    'INVALID_MARKET' => '\\ccxt\\BadSymbol',
                    'INVALID_PRICE' => '\\ccxt\\BadRequest',
                    'INVALID_POSITION_ID' => '\\ccxt\\BadRequest',
                    'INVALID_QUANTITY' => '\\ccxt\\BadRequest',
                    'INVALID_RANGE' => '\\ccxt\\BadRequest',
                    'INVALID_SIGNATURE' => '\\ccxt\\AuthenticationError',
                    'INVALID_SOURCE' => '\\ccxt\\BadRequest',
                    'INVALID_SYMBOL' => '\\ccxt\\BadSymbol',
                    'INVALID_TWO_FACTOR_CODE' => '\\ccxt\\BadRequest',
                    'LEND_LIMIT' => '\\ccxt\\BadRequest',
                    'LEND_REQUIRES_BORROW_REPAY' => '\\ccxt\\BadRequest',
                    'MAINTENANCE' => '\\ccxt\\ExchangeError',
                    'MAX_LEVERAGE_REACHED' => '\\ccxt\\InsufficientFunds',
                    'NOT_IMPLEMENTED' => '\\ccxt\\OperationFailed',
                    'ORDER_LIMIT' => '\\ccxt\\OperationRejected',
                    'POSITION_LIMIT' => '\\ccxt\\OperationRejected',
                    'PRECONDITION_FAILED' => '\\ccxt\\OperationFailed',
                    'RESOURCE_NOT_FOUND' => '\\ccxt\\ExchangeNotAvailable',
                    'SERVER_ERROR' => '\\ccxt\\NetworkError',
                    'TIMEOUT' => '\\ccxt\\RequestTimeout',
                    'TOO_MANY_REQUESTS' => '\\ccxt\\RateLimitExceeded',
                    'TRADING_PAUSED' => '\\ccxt\\ExchangeNotAvailable',
                    'UNAUTHORIZED' => '\\ccxt\\AuthenticationError',
                ),
                // Bad Request parse request payload error => failed to parse "MarketSymbol" => Invalid market symbol (occurred while parsing "OrderExecutePayload")
                // failed to parse parameter `interval` => failed to parse "KlineInterval" => Expect a valid enumeration value.
                'broad' => array(),
            ),
        ));
    }

    public function fetch_currencies($params = array ()): ?array {
        /**
         * fetches all available currencies on an exchange
         *
         * @see https://docs.backpack.exchange/#tag/Assets
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an associative dictionary of currencies
         */
        $response = $this->publicGetApiV1Assets ($params);
        //
        //     array(
        //         {
        //             "coingeckoId" => "jito-governance-token",
        //             "displayName" => "Jito",
        //             "symbol" => "JTO",
        //             "tokens" => array(
        //                 {
        //                     "blockchain" => "Solana",
        //                     "contractAddress" => "jtojtomepa8beP8AuQc6eXt5FriJwfFMwQx2v2f9mCL",
        //                     "depositEnabled" => true,
        //                     "displayName" => "Jito",
        //                     "maximumWithdrawal" => null,
        //                     "minimumDeposit" => "0.29",
        //                     "minimumWithdrawal" => "0.58",
        //                     "withdrawEnabled" => true,
        //                     "withdrawalFee" => "0.29"
        //                 }
        //             )
        //         }
        //         ...
        //     )
        //
        $result = array();
        for ($i = 0; $i < count($response); $i++) {
            $currecy = $response[$i];
            $currencyId = $this->safe_string($currecy, 'symbol');
            $code = $this->safe_currency_code($currencyId);
            $networks = $this->safe_list($currecy, 'tokens', array());
            $parsedNetworks = array();
            for ($j = 0; $j < count($networks); $j++) {
                $network = $networks[$j];
                $networkId = $this->safe_string($network, 'blockchain');
                $networkIdLowerCase = $this->safe_string_lower($network, 'blockchain');
                $networkCode = $this->network_id_to_code($networkIdLowerCase);
                $parsedNetworks[$networkCode] = array(
                    'id' => $networkId,
                    'network' => $networkCode,
                    'limits' => array(
                        'withdraw' => array(
                            'min' => $this->safe_number($network, 'minimumWithdrawal'),
                            'max' => $this->parse_number($this->omit_zero($this->safe_string($network, 'maximumWithdrawal'))),
                        ),
                        'deposit' => array(
                            'min' => $this->safe_number($network, 'minimumDeposit'),
                            'max' => null,
                        ),
                    ),
                    'active' => null,
                    'deposit' => $this->safe_bool($network, 'depositEnabled'),
                    'withdraw' => $this->safe_bool($network, 'withdrawEnabled'),
                    'fee' => $this->safe_number($network, 'withdrawalFee'),
                    'precision' => null,
                    'info' => $network,
                );
            }
            $active = null;
            $deposit = null;
            $withdraw = null;
            if ($this->is_empty($parsedNetworks)) { // if $networks are not provided
                $active = false;
                $deposit = false;
                $withdraw = false;
            }
            $result[$code] = $this->safe_currency_structure(array(
                'id' => $currencyId,
                'code' => $code,
                'precision' => null,
                'type' => 'crypto', // todo check if it is always crypto
                'name' => $this->safe_string($currecy, 'displayName'),
                'active' => $active,
                'deposit' => $deposit,
                'withdraw' => $withdraw,
                'fee' => null,
                'limits' => array(
                    'deposit' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'withdraw' => array(
                        'min' => null,
                        'max' => null,
                    ),
                ),
                'networks' => $parsedNetworks,
                'info' => $currecy,
            ));
        }
        return $result;
    }

    public function fetch_markets($params = array ()): array {
        /**
         * retrieves data on all markets for bitbank
         *
         * @see https://docs.backpack.exchange/#tag/Markets/operation/get_markets
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing market data
         */
        if ($this->options['adjustForTimeDifference']) {
            $this->load_time_difference();
        }
        $response = $this->publicGetApiV1Markets ($params);
        return $this->parse_markets($response);
    }

    public function parse_market(array $market): array {
        //
        //     array(
        //         {
        //             "baseSymbol" => "SOL",
        //             "createdAt" => "2025-01-21T06:34:54.691858",
        //             "filters" => array(
        //                 "price" => array(
        //                     "borrowmarketFeeMaxMultiplier" => null,
        //                     "borrowmarketFeeMinMultiplier" => null,
        //                     "maxImpactMultiplier" => "1.03",
        //                     "maxMultiplier" => "1.25",
        //                     "maxPrice" => null,
        //                     "meanMarkPriceBand" => array(
        //                         "maxMultiplier" => "1.15",
        //                         "minMultiplier" => "0.9"
        //                     ),
        //                     "meanPremiumBand" => null,
        //                     "minImpactMultiplier" => "0.97",
        //                     "minMultiplier" => "0.75",
        //                     "minPrice" => "0.01",
        //                     "tickSize" => "0.01"
        //                 ),
        //                 "quantity" => array(
        //                     "maxQuantity" => null,
        //                     "minQuantity" => "0.01",
        //                     "stepSize" => "0.01"
        //                 }
        //             ),
        //             "fundingInterval" => 28800000,
        //             "fundingRateLowerBound" => null,
        //             "fundingRateUpperBound" => null,
        //             "imfFunction" => null,
        //             "marketType" => "SPOT",
        //             "mmfFunction" => null,
        //             "openInterestLimit" => "0",
        //             "orderBookState" => "Open",
        //             "quoteSymbol" => "USDC",
        //             "symbol" => "SOL_USDC"
        //         ),
        //         {
        //             "baseSymbol" => "SOL",
        //             "createdAt" => "2025-01-21T06:34:54.691858",
        //             "filters" => {
        //                 "price" => array(
        //                     "borrowEntryFeeMaxMultiplier" => null,
        //                     "borrowEntryFeeMinMultiplier" => null,
        //                     "maxImpactMultiplier" => "1.03",
        //                     "maxMultiplier" => "1.25",
        //                     "maxPrice" => "1000",
        //                     "meanMarkPriceBand" => array(
        //                         "maxMultiplier" => "1.1",
        //                         "minMultiplier" => "0.9"
        //                     ),
        //                     "meanPremiumBand" => array(
        //                         "tolerancePct" => "0.05"
        //                     ),
        //                     "minImpactMultiplier" => "0.97",
        //                     "minMultiplier" => "0.75",
        //                     "minPrice" => "0.01",
        //                     "tickSize" => "0.01"
        //                 ),
        //                 "quantity" => array(
        //                     "maxQuantity" => null,
        //                     "minQuantity" => "0.01",
        //                     "stepSize" => "0.01"
        //                 }
        //             ),
        //             "fundingInterval" => "28800000",
        //             "fundingRateLowerBound" => "-100",
        //             "fundingRateUpperBound" => "100",
        //             "imfFunction" => array(
        //                 "base" => "0.02",
        //                 "factor" => "0.0001275",
        //                 "type" => "sqrt"
        //             ),
        //             "marketType" => "PERP",
        //             "mmfFunction" => array(
        //                 "base" => "0.0125",
        //                 "factor" => "0.0000765",
        //                 "type" => "sqrt"
        //             ),
        //             "openInterestLimit" => "4000000",
        //             "orderBookState" => "Open",
        //             "quoteSymbol" => "USDC",
        //             "symbol" => "SOL_USDC_PERP"
        //         }
        //     )
        //
        $id = $this->safe_string($market, 'symbol');
        $baseId = $this->safe_string($market, 'baseSymbol');
        $quoteId = $this->safe_string($market, 'quoteSymbol');
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $symbol = $base . '/' . $quote;
        $filters = $this->safe_dict($market, 'filters', array());
        $priceFilter = $this->safe_dict($filters, 'price', array());
        $maxPrice = $this->safe_number($priceFilter, 'maxPrice');
        $minPrice = $this->safe_number($priceFilter, 'minPrice');
        $pricePrecision = $this->safe_number($priceFilter, 'tickSize');
        $quantityFilter = $this->safe_dict($filters, 'quantity', array());
        $maxQuantity = $this->safe_number($quantityFilter, 'maxQuantity');
        $minQuantity = $this->safe_number($quantityFilter, 'minQuantity');
        $amountPrecision = $this->safe_number($quantityFilter, 'stepSize');
        $typeOfMarket = $this->parse_market_type($this->safe_string($market, 'marketType'));
        $linear = null;
        $inverse = null;
        $settle = null;
        $settleId = null;
        $contractSize = null;
        if ($typeOfMarket === 'spot') {
            $type = 'spot';
        } elseif ($typeOfMarket === 'swap') {
            $type = 'swap';
            $linear = true;
            $inverse = false;
            $settleId = $this->safe_string($market, 'quoteSymbol');
            $settle = $this->safe_currency_code($settleId);
            $symbol .= ':' . $settle;
            $contractSize = 1;
        }
        $orderBookState = $this->safe_string($market, 'orderBookState');
        return $this->safe_market_structure(array(
            'id' => $id,
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'settle' => $settle,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'settleId' => $settleId,
            'type' => $type,
            'spot' => $type === 'spot',
            'margin' => $type === 'spot', // todo check if margin is supported for all markets
            'swap' => $type === 'swap',
            'future' => false,
            'option' => false,
            'active' => $orderBookState === 'Open',
            'contract' => $type !== 'spot',
            'linear' => $linear,
            'inverse' => $inverse,
            'taker' => null, // todo check commission
            'maker' => null, // todo check commission
            'contractSize' => $contractSize,
            'expiry' => null,
            'expiryDatetime' => null,
            'strike' => null,
            'optionType' => null,
            'precision' => array(
                'amount' => $amountPrecision,
                'price' => $pricePrecision,
            ),
            'limits' => array(
                'leverage' => array(
                    'min' => null,
                    'max' => null,
                ),
                'amount' => array(
                    'min' => $minQuantity,
                    'max' => $maxQuantity,
                ),
                'price' => array(
                    'min' => $minPrice,
                    'max' => $maxPrice,
                ),
                'cost' => array(
                    'min' => null,
                    'max' => null,
                ),
            ),
            'created' => $this->parse8601($this->safe_string($market, 'createdAt')),
            'info' => $market,
        ));
    }

    public function parse_market_type($type) {
        $types = array(
            'SPOT' => 'spot',
            'PERP' => 'swap',
            // current $types are described in the docs, but the exchange returns only 'SPOT' and 'PERP'
            // 'IPERP' => 'swap',
            // 'DATED' => 'swap',
            // 'PREDICTION' => 'swap',
            // 'RFQ' => 'swap',
        );
        return $this->safe_string($types, $type, $type);
    }

    public function fetch_tickers(?array $symbols = null, $params = array ()): array {
        /**
         *
         * @see https://docs.backpack.exchange/#tag/Markets/operation/get_tickers
         *
         * fetches price $tickers for multiple markets, statistical information calculated over the past 24 hours for each market
         * @param {string[]|null} $symbols unified $symbols of the markets to fetch the ticker for, all market $tickers are returned if not assigned
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structures~
         */
        $this->load_markets();
        $request = array();
        $response = $this->publicGetApiV1Tickers ($this->extend($request, $params));
        $tickers = $this->parse_tickers($response);
        return $this->filter_by_array_tickers($tickers, 'symbol', $symbols);
    }

    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://docs.backpack.exchange/#tag/Markets/operation/get_ticker
         *
         * @param {string} $symbol unified $symbol of the $market to fetch the ticker for
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = $this->publicGetApiV1Ticker ($this->extend($request, $params));
        return $this->parse_ticker($response, $market);
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        //
        // fetchTicker/fetchTickers
        //
        //     array(
        //         "firstPrice" => "327.38",
        //         "high" => "337.99",
        //         "lastPrice" => "317.14",
        //         "low" => "300.01",
        //         "priceChange" => "-10.24",
        //         "priceChangePercent" => "-0.031279",
        //         "quoteVolume" => "21584.32278",
        //         "symbol" => "AAVE_USDC",
        //         "trades" => "245",
        //         "volume" => "65.823"
        //     ), ...
        //
        $marketId = $this->safe_string($ticker, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $this->safe_symbol($marketId, $market);
        $open = $this->safe_string($ticker, 'firstPrice');
        $last = $this->safe_string($ticker, 'lastPrice');
        $high = $this->safe_string($ticker, 'high');
        $low = $this->safe_string($ticker, 'low');
        $baseVolume = $this->safe_string($ticker, 'volume');
        $quoteVolume = $this->safe_string($ticker, 'quoteVolume');
        $percentage = $this->safe_string($ticker, 'priceChangePercent');
        $change = $this->safe_string($ticker, 'priceChange');
        return $this->safe_ticker(array(
            'symbol' => $symbol,
            'timestamp' => null,
            'datetime' => null,
            'high' => $high,
            'low' => $low,
            'bid' => null,
            'bidVolume' => null,
            'ask' => null,
            'askVolume' => null,
            'vwap' => null,
            'open' => $open,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => $change,
            'percentage' => $percentage,
            'average' => null,
            'baseVolume' => $baseVolume,
            'quoteVolume' => $quoteVolume,
            'markPrice' => null,
            'indexPrice' => 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://docs.backpack.exchange/#tag/Markets/operation/get_depth
         *
         * @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 (default 100, max 200)
         * @param {array} [$params] extra parameters specific to the bitteam api endpoint
         * @return {array} A dictionary of {@link https://github.com/ccxt/ccxt/wiki/Manual#order-book-structure order book structures} indexed by $market symbols
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = $this->publicGetApiV1Depth ($this->extend($request, $params));
        //
        //     {
        //         "asks" => [
        //             ["118318.3","0.00633"],
        //             ["118567.2","0.08450"]
        //         ],
        //         "bids" => [
        //             ["1.0","0.38647"],
        //             ["12.9","1.00000"]
        //         ],
        //         "lastUpdateId":"1504999670",
        //         "timestamp":1753102447307501
        //     }
        //
        $microseconds = $this->safe_integer($response, 'timestamp');
        $timestamp = $this->parse_to_int($microseconds / 1000);
        $orderbook = $this->parse_order_book($response, $symbol, $timestamp);
        $orderbook['nonce'] = $this->safe_integer($response, 'lastUpdateId');
        return $orderbook;
    }

    public function fetch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches historical candlestick data containing the open, high, low, and close $price, and the volume of a $market
         *
         * @see https://docs.backpack.exchange/#tag/Markets/operation/get_klines
         *
         * @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 seconds 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 bitteam api endpoint
         * @return {int[][]} A list of candles ordered, open, high, low, close, volume
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $interval = $this->safe_string($this->timeframes, $timeframe, $timeframe);
        $request = array(
            'symbol' => $market['id'],
            'interval' => $interval,
        );
        $until = null;
        list($until, $params) = $this->handle_option_and_params($params, 'fetchOHLCV', 'until');
        if ($until !== null) {
            $request['endTime'] = $this->parse_to_int($until / 1000); // convert milliseconds to seconds
        }
        $defaultLimit = 100;
        if ($since === null) {
            if ($limit === null) {
                $limit = $defaultLimit;
            }
            $duration = $this->parse_timeframe($timeframe);
            $endTime = $until ? $this->parse_to_int($until / 1000) : $this->seconds();
            $startTime = $endTime - ($limit * $duration);
            $request['startTime'] = $startTime;
        } else {
            $request['startTime'] = $this->parse_to_int($since / 1000); // convert milliseconds to seconds
        }
        $price = $this->safe_string($params, 'price');
        if ($price !== null) {
            $request['priceType'] = $this->capitalize($price);
            $params = $this->omit($params, 'price');
        }
        $response = $this->publicGetApiV1Klines ($this->extend($request, $params));
        return $this->parse_ohlcvs($response, $market, $timeframe, $since, $limit);
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        //
        //     array(
        //         array(
        //             "close" => "118294.6",
        //             "end" => "2025-07-19 13:12:00",
        //             "high" => "118297.6",
        //             "low" => "118237.5",
        //             "open" => "118238",
        //             "quoteVolume" => "4106.558156",
        //             "start" => "2025-07-19 13:09:00",
        //             "trades" => "12",
        //             "volume" => "0.03473"
        //         ),
        //         ...
        //     )
        //
        return array(
            $this->parse8601($this->safe_string($ohlcv, 'start')),
            $this->safe_number($ohlcv, 'open'),
            $this->safe_number($ohlcv, 'high'),
            $this->safe_number($ohlcv, 'low'),
            $this->safe_number($ohlcv, 'close'),
            $this->safe_number($ohlcv, 'volume'),
        );
    }

    public function fetch_funding_rate(string $symbol, $params = array ()): array {
        /**
         * fetch the current funding rate
         *
         * @see https://docs.backpack.exchange/#tag/Markets/operation/get_mark_prices
         *
         * @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);
        if ($market['spot']) {
            throw new BadRequest($this->id . ' fetchFundingRate() $symbol does not support $market ' . $symbol);
        }
        $request = array(
            'symbol' => $market['id'],
        );
        $response = $this->publicGetApiV1MarkPrices ($this->extend($request, $params));
        $data = $this->safe_dict($response, 0, array());
        return $this->parse_funding_rate($data, $market);
    }

    public function parse_funding_rate($contract, ?array $market = null): array {
        //
        //     {
        //         "fundingRate" => "0.0001",
        //         "indexPrice" => "118333.18643195",
        //         "markPrice" => "118343.51853741",
        //         "nextFundingTimestamp" => 1753113600000,
        //         "symbol" => "BTC_USDC_PERP"
        //     }
        //
        $marketId = $this->safe_string($contract, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $this->safe_symbol($marketId, $market);
        $nextFundingTimestamp = $this->safe_integer($contract, 'nextFundingTimestamp');
        return array(
            'info' => $contract,
            'symbol' => $symbol,
            'markPrice' => $this->safe_number($contract, 'markPrice'),
            'indexPrice' => $this->safe_number($contract, 'indexPrice'),
            'interestRate' => null,
            'estimatedSettlePrice' => null,
            'timestamp' => null,
            'datetime' => null,
            'fundingRate' => $this->safe_number($contract, 'fundingRate'),
            'fundingTimestamp' => null,
            'fundingDatetime' => null,
            'nextFundingRate' => null,
            'nextFundingTimestamp' => $nextFundingTimestamp,
            'nextFundingDatetime' => $this->iso8601($nextFundingTimestamp),
            'previousFundingRate' => null,
            'previousFundingTimestamp' => null,
            'previousFundingDatetime' => null,
            'interval' => '1h',
        );
    }

    public function fetch_open_interest(string $symbol, $params = array ()) {
        /**
         * Retrieves the open $interest of a derivative trading pair
         *
         * @see https://docs.backpack.exchange/#tag/Markets/operation/get_open_interest
         *
         * @param {string} $symbol Unified CCXT $market $symbol
         * @param {array} [$params] exchange specific parameters
         * @return {array} an open $interest structurearray(@link https://docs.ccxt.com/#/?id=$interest-history-structure)
         */
        $this->load_markets();
        $market = $this->market($symbol);
        if ($market['spot']) {
            throw new BadRequest($this->id . ' fetchOpenInterest() $symbol does not support $market ' . $symbol);
        }
        $request = array(
            'symbol' => $market['id'],
        );
        $response = $this->publicGetApiV1OpenInterest ($this->extend($request, $params));
        $interest = $this->safe_dict($response, 0, array());
        return $this->parse_open_interest($interest, $market);
    }

    public function parse_open_interest($interest, ?array $market = null) {
        //
        //     array(
        //         {
        //             "openInterest" => "1273.85214",
        //             "symbol" => "BTC_USDC_PERP",
        //             "timestamp":1753105735301
        //         }
        //     )
        //
        $timestamp = $this->safe_integer($interest, 'timestamp');
        $openInterest = $this->safe_number($interest, 'openInterest');
        return $this->safe_open_interest(array(
            'symbol' => $market['symbol'],
            'openInterestAmount' => null,
            'openInterestValue' => $openInterest,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'info' => $interest,
        ), $market);
    }

    public function fetch_funding_rate_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetches historical funding $rate prices
         *
         * @see https://docs.backpack.exchange/#tag/Markets/operation/get_funding_interval_rates
         *
         * @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 funding $rate structures
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @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();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        if ($limit !== null) {
            $request['limit'] = min ($limit, 1000); // api maximum 1000
        }
        $response = $this->publicGetApiV1FundingRates ($this->extend($request, $params));
        //
        //     array(
        //         {
        //             "fundingRate" => "0.0001",
        //             "intervalEndTimestamp" => "2025-07-22T00:00:00",
        //             "symbol" => "BTC_USDC_PERP"
        //         }
        //     )
        //
        $rates = array();
        for ($i = 0; $i < count($response); $i++) {
            $rate = $response[$i];
            $datetime = $this->safe_string($rate, 'intervalEndTimestamp');
            $timestamp = $this->parse8601($datetime);
            $rates[] = array(
                'info' => $rate,
                'symbol' => $market['symbol'],
                'fundingRate' => $this->safe_number($rate, 'fundingRate'),
                'timestamp' => $timestamp,
                'datetime' => $datetime,
            );
        }
        $sorted = $this->sort_by($rates, 'timestamp');
        return $this->filter_by_symbol_since_limit($sorted, $market['symbol'], $since, $limit);
    }

    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://docs.backpack.exchange/#tag/Trades/operation/get_recent_trades
         * @see https://docs.backpack.exchange/#tag/Trades/operation/get_historical_trades
         *
         * @param {string} $symbol unified $symbol of the $market to fetch trades for
         * @param {int} [$since] timestamp in ms of the earliest trade to fetch
         * @param {int} [$limit] the maximum amount of trades to fetch
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->offset] the number of trades to skip, default is 0
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-trades trade structures~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        if ($limit !== null) {
            $request['limit'] = min ($limit, 1000); // api maximum 1000
        }
        $response = null;
        $offset = $this->safe_integer($params, 'offset');
        if ($offset !== null) {
            $response = $this->publicGetApiV1TradesHistory ($this->extend($request, $params));
        } else {
            $response = $this->publicGetApiV1Trades ($this->extend($request, $params));
        }
        return $this->parse_trades($response, $market, $since, $limit);
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch all trades made by the user
         *
         * @see https://docs.backpack.exchange/#tag/History/operation/get_fills
         *
         * @param {string} $symbol unified $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch trades for
         * @param {int} [$limit] the maximum number of trades structures to retrieve (default 100, max 1000)
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] the latest time in ms to fetch trades for
         * @param {string} [$params->fillType] 'User' (default) 'BookLiquidation' or 'Adl' or 'Backstop' or 'Liquidation' or 'AllLiquidation' or 'CollateralConversion' or 'CollateralConversionAndSpotLiquidation'
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
         */
        $this->load_markets();
        $request = array();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        if ($since !== null) {
            $request['from'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $until = $this->safe_integer($params, 'until');
        if ($until !== null) {
            $params = $this->omit($params, array( 'until' ));
            $request['to'] = $until;
        }
        $fillType = $this->safe_string($params, 'fillType');
        if ($fillType === null) {
            $request['fillType'] = 'User'; // default
        }
        $response = $this->privateGetWapiV1HistoryFills ($this->extend($request, $params));
        return $this->parse_trades($response, $market, $since, $limit);
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // fetchTrades
        //     {
        //         "id" => 8721564,
        //         "isBuyerMaker" => false,
        //         "price" => "117427.6",
        //         "quantity" => "0.00016",
        //         "quoteQuantity" => "18.788416",
        //         "timestamp" => 1753123916818
        //     }
        //
        // fetchMyTrades
        //     {
        //         "clientId" => null,
        //         "fee" => "0.004974",
        //         "feeSymbol" => "USDC",
        //         "isMaker" => false,
        //         "orderId" => "4238907375",
        //         "price" => "3826.15",
        //         "quantity" => "0.0026",
        //         "side" => "Bid",
        //         "symbol" => "ETH_USDC_PERP",
        //         "systemOrderType" => null,
        //         "timestamp" => "2025-07-27T17:39:00.092",
        //         "tradeId" => 9748827
        //     }
        //
        $id = $this->safe_string_2($trade, 'id', 'tradeId');
        $marketId = $this->safe_string($trade, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $price = $this->safe_string($trade, 'price');
        $amount = $this->safe_string($trade, 'quantity');
        $isMaker = $this->safe_bool($trade, 'isMaker');
        $takerOrMaker = $isMaker ? 'maker' : 'taker';
        $orderId = $this->safe_string($trade, 'orderId');
        $side = $this->parse_order_side($this->safe_string($trade, 'side'));
        $fee = null;
        $feeAmount = $this->safe_string($trade, 'fee');
        $timestamp = $this->safe_integer($trade, 'timestamp');
        if ($feeAmount !== null) {
            // if fetchMyTrades
            $datetime = $this->safe_string($trade, 'timestamp');
            $timestamp = $this->parse8601($datetime);
        }
        $feeSymbol = $this->safe_currency_code($this->safe_string($trade, 'feeSymbol'));
        if ($feeAmount !== null) {
            $fee = array(
                'cost' => $feeAmount,
                'currency' => $feeSymbol,
                'rate' => null,
            );
        }
        return $this->safe_trade(array(
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $market['symbol'],
            'id' => $id,
            'order' => $orderId,
            'type' => null,
            'side' => $side,
            'takerOrMaker' => $takerOrMaker,
            'price' => $price,
            'amount' => $amount,
            'cost' => null,
            'fee' => $fee,
        ), $market);
    }

    public function fetch_status($params = array ()) {
        /**
         * the latest known information on the availability of the exchange API
         *
         * @see https://docs.backpack.exchange/#tag/System/operation/get_status
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=exchange-$status-structure $status structure~
         */
        $response = $this->publicGetApiV1Status ($params);
        //
        //     {
        //         "message":null,
        //         "status":"Ok"
        //     }
        //
        $status = $this->safe_string($response, 'status');
        return array(
            'status' => strtolower($status),
            'updated' => null,
            'eta' => null,
            'url' => null,
            'info' => $response,
        );
    }

    public function fetch_time($params = array ()): ?int {
        /**
         * fetches the current integer timestamp in milliseconds from the exchange server
         *
         * @see https://developer-pro.bitmart.com/en/spot/#get-system-time
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {int} the current integer timestamp in milliseconds from the exchange server
         */
        $response = $this->publicGetApiV1Time ($params);
        //
        //     1753131712992
        //
        return $this->safe_integer($response, 0, $this->milliseconds());
    }

    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://docs.backpack.exchange/#tag/Capital/operation/get_balances
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
         */
        $this->load_markets();
        $response = $this->privateGetApiV1Capital ($params);
        return $this->parse_balance($response);
    }

    public function parse_balance($response): array {
        //
        //     {
        //         "USDC" => {
        //             "available" => "120",
        //             "locked" => "0",
        //             "staked" => "0"
        //         }
        //     }
        //
        $balanceKeys = is_array($response) ? array_keys($response) : array();
        $result = array();
        for ($i = 0; $i < count($balanceKeys); $i++) {
            $id = $balanceKeys[$i];
            $code = $this->safe_currency_code($id);
            $balance = $response[$id];
            $account = $this->account();
            $locked = $this->safe_string($balance, 'locked');
            $staked = $this->safe_string($balance, 'staked');
            $used = Precise::string_add($locked, $staked);
            $account['free'] = $this->safe_string($balance, 'available');
            $account['used'] = $used;
            $result[$code] = $account;
        }
        return $this->safe_balance($result);
    }

    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://docs.backpack.exchange/#tag/Capital/operation/get_deposits
         *
         * @param {string} $code unified $currency $code
         * @param {int} [$since] the earliest time in ms to fetch deposits for
         * @param {int} [$limit] the maximum number of deposits structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] the latest time in ms to fetch entries for
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $this->load_markets();
        $request = array(
        );
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
        }
        if ($since !== null) {
            $request['from'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit; // default 100, max 1000
        }
        $until = null;
        list($until, $params) = $this->handle_option_and_params($params, 'fetchDeposits', 'until');
        if ($until !== null) {
            $request['endTime'] = $until;
        }
        $response = $this->privateGetWapiV1CapitalDeposits ($this->extend($request, $params));
        return $this->parse_transactions($response, $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://docs.backpack.exchange/#tag/Capital/operation/get_withdrawals
         *
         * @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)
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $this->load_markets();
        $request = array();
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
        }
        if ($since !== null) {
            $request['from'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $until = null;
        list($until, $params) = $this->handle_option_and_params($params, 'fetchWithdrawals', 'until');
        if ($until !== null) {
            $request['to'] = $until;
        }
        $response = $this->privateGetWapiV1CapitalWithdrawals ($this->extend($request, $params));
        return $this->parse_transactions($response, $currency, $since, $limit);
    }

    public function withdraw(string $code, float $amount, string $address, ?string $tag = null, $params = array ()): array {
        /**
         * make a withdrawal
         *
         * @see https://docs.backpack.exchange/#tag/Capital/operation/request_withdrawal
         *
         * @param {string} $code unified $currency $code
         * @param {float} $amount the $amount to withdraw
         * @param {string} $address the $address to withdraw to
         * @param {string} $tag
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->network] the network to withdraw on (mandatory)
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structure~
         */
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'symbol' => $currency['id'],
            'amount' => $this->number_to_string($amount),
            'address' => $address,
        );
        if ($tag !== null) {
            $request['clientId'] = $tag; // memo or $tag
        }
        list($networkCode, $query) = $this->handle_network_code_and_params($params);
        $networkId = $this->network_code_to_id($networkCode);
        if ($networkId === null) {
            throw new BadRequest($this->id . ' withdraw() requires a network parameter');
        }
        $request['blockchain'] = $networkId;
        $response = $this->privatePostWapiV1CapitalWithdrawals ($this->extend($request, $query));
        return $this->parse_transaction($response, $currency);
    }

    public function parse_transaction($transaction, ?array $currency = null): array {
        //
        // fetchDeposits
        //     array(
        //         {
        //             "createdAt" => "2025-07-23T13:55:54.267",
        //             "fiatAmount" => null,
        //             "fiatCurrency" => null,
        //             "fromAddress" => "0x2e3ab3e88a7dbdc763aadf5b28c18fb085af420a",
        //             "id" => 6695353,
        //             "institutionBic" => null,
        //             "platformMemo" => null,
        //             "quantity" => "120",
        //             "source" => "ethereum",
        //             "status" => "confirmed",
        //             "symbol" => "USDC",
        //             "toAddress" => "0xfBe7CbfCde93c8a4204a4be6B56732Eb32690170",
        //             "transactionHash" => "0x58edaac415398d617b34c6673fffcaf0024990d5700565030119db5cbf3765d1"
        //         }
        //     )
        //
        // withdraw
        //     {
        //         "accountIdentifier" => null,
        //         "bankIdentifier" => null,
        //         "bankName" => null,
        //         "blockchain" => "Ethereum",
        //         "clientId" => null,
        //         "createdAt" => "2025-08-13T19:27:13.817",
        //         "fee" => "3",
        //         "fiatFee" => null,
        //         "fiatState" => null,
        //         "fiatSymbol" => null,
        //         "id" => 5479929,
        //         "identifier" => null,
        //         "isInternal" => false,
        //         "providerId" => null,
        //         "quantity" => "10",
        //         "status" => "pending",
        //         "subaccountId" => null,
        //         "symbol" => "USDC",
        //         "toAddress" => "0x0ad42b8e602c2d3d475ae52d678cf63d84ab2749",
        //         "transactionHash" => null,
        //         "triggerAt" => null
        //     }
        //
        // fetchWithdrawals
        //     array(
        //         {
        //             "accountIdentifier" => null,
        //             "bankIdentifier" => null,
        //             "bankName" => null,
        //             "blockchain" => "Ethereum",
        //             "clientId" => null,
        //             "createdAt" => "2025-08-13T19:27:13.817",
        //             "fee" => "3",
        //             "fiatFee" => null,
        //             "fiatState" => null,
        //             "fiatSymbol" => null,
        //             "id" => 5479929,
        //             "identifier" => null,
        //             "isInternal" => false,
        //             "providerId" => null,
        //             "quantity" => "10",
        //             "status" => "confirmed",
        //             "subaccountId" => null,
        //             "symbol" => "USDC",
        //             "toAddress" => "0x0ad42b8e602c2d3d475ae52d678cf63d84ab2749",
        //             "transactionHash" => "0x658b6d082af4afa0d3cf85caf344ff7c19d980117726bf193b00d8850f8746a1",
        //             "triggerAt" => null
        //         }
        //     )
        //
        $status = $this->parse_transaction_status($this->safe_string($transaction, 'status'));
        $id = $this->safe_string($transaction, 'id');
        $txid = $this->safe_string($transaction, 'transactionHash');
        $coin = $this->safe_string($transaction, 'symbol');
        $code = $this->safe_currency_code($coin, $currency);
        $timestamp = $this->parse8601($this->safe_string($transaction, 'createdAt'));
        $amount = $this->safe_number($transaction, 'quantity');
        $networkId = $this->safe_string_lower_2($transaction, 'source', 'blockchain');
        $network = $this->network_id_to_code($networkId);
        $addressTo = $this->safe_string($transaction, 'toAddress');
        $addressFrom = $this->safe_string($transaction, 'fromAddress');
        $tag = $this->safe_string($transaction, 'platformMemo');
        $feeCost = $this->safe_number($transaction, 'fee');
        $internal = $this->safe_bool($transaction, 'isInternal', false);
        $fee = null;
        if ($feeCost !== null) {
            $fee = array(
                'cost' => $feeCost,
                'currency' => $code,
            );
        }
        return array(
            'info' => $transaction,
            'id' => $id,
            'txid' => $txid,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'network' => $network,
            'address' => null,
            'addressTo' => $addressTo,
            'addressFrom' => $addressFrom,
            'tag' => $tag,
            'tagTo' => null,
            'tagFrom' => null,
            'type' => null,
            'amount' => $amount,
            'currency' => $code,
            'status' => $status,
            'updated' => null,
            'internal' => $internal,
            'comment' => null,
            'fee' => $fee,
        );
    }

    public function parse_transaction_status(?string $status) {
        $statuses = array(
            'cancelled' => 'cancelled',
            'confirmed' => 'ok',
            'declined' => 'declined',
            'expired' => 'expired',
            'initiated' => 'initiated',
            'pending' => 'pending',
            'refunded' => 'refunded',
            'information required' => 'pending',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function fetch_deposit_address(string $code, $params = array ()): array {
        /**
         * fetch the deposit address for a $currency associated with this account
         *
         * @see https://docs.backpack.exchange/#tag/Capital/operation/get_deposit_address
         *
         * @param {string} $code unified $currency $code
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->networkCode] the network to fetch the deposit address (mandatory)
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=address-structure address structure~
         */
        $this->load_markets();
        $networkCode = null;
        list($networkCode, $params) = $this->handle_network_code_and_params($params);
        if ($networkCode === null) {
            throw new ArgumentsRequired($this->id . ' fetchDepositAddress() requires a network parameter, see https://docs.ccxt.com/#/?id=network-codes');
        }
        $currency = $this->currency($code);
        $request = array(
            'blockchain' => $this->network_code_to_id($networkCode),
        );
        $response = $this->privateGetWapiV1CapitalDepositAddress ($this->extend($request, $params));
        return $this->parse_deposit_address($response, $currency);
    }

    public function parse_deposit_address($depositAddress, ?array $currency = null): array {
        //
        //     {
        //         "address" => "0xfBe7CbfCde93c8a4204a4be6B56732Eb32690170"
        //     }
        //
        $address = $this->safe_string($depositAddress, 'address');
        $currencyId = $this->safe_string($depositAddress, 'currency');
        $currency = $this->safe_currency($currencyId, $currency);
        return array(
            'info' => $depositAddress,
            'currency' => $currency['code'],
            'network' => null, // network is not returned by the API
            'address' => $address,
            'tag' => null,
        );
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()): array {
        /**
         * create a trade order
         *
         * @see https://docs.backpack.exchange/#tag/Order/operation/execute_order
         *
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market' or 'limit'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much of currency you want to trade in units of base currency
         * @param {float} [$price] the $price at which the order is to be fullfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {float} [$params->cost] *$market orders only* the cost of the order in units of the quote currency (could be used instead of $amount)
         * @param {int} [$params->clientOrderId] a unique id for the order
         * @param {boolean} [$params->postOnly] true to place a post only order
         * @param {string} [$params->timeInForce] 'GTC', 'IOC', 'FOK' or 'PO'
         * @param {bool} [$params->reduceOnly] *contract only* Indicates if this order is to reduce the size of a position
         * @param {string} [$params->selfTradePrevention] one of EXPIRE_MAKER, EXPIRE_TAKER or EXPIRE_BOTH
         * @param {bool} [$params->autoLend] *spot margin only* if true then the order can lend
         * @param {bool} [$params->autoLendRedeem] *spot margin only* if true then the order can redeem a lend if required
         * @param {bool} [$params->autoBorrow] *spot margin only* if true then the order can borrow
         * @param {bool} [$params->autoBorrowRepay] *spot margin only* if true then the order can repay a borrow
         * @param {float} [$params->triggerPrice] the $price that a trigger order is triggered at
         * @param {array} [$params->takeProfit] *swap markets only - takeProfit object in $params* containing the triggerPrice at which the attached take profit order will be triggered
         * @param {float} [$params->takeProfit.triggerPrice] take profit trigger $price
         * @param {float} [$params->takeProfit.price] take profit order $price (if not provided the order will be a $market order)
         * @param {array} [$params->stopLoss] *swap markets only - stopLoss object in $params* containing the triggerPrice at which the attached stop loss order will be triggered
         * @param {float} [$params->stopLoss.triggerPrice] stop loss trigger $price
         * @param {float} [$params->stopLoss.price] stop loss order $price (if not provided the order will be a $market order)
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $orderRequest = $this->create_order_request($symbol, $type, $side, $amount, $price, $params);
        $response = $this->privatePostApiV1Order ($orderRequest);
        return $this->parse_order($response, $market);
    }

    public function create_orders(array $orders, $params = array ()) {
        /**
         * create a list of trade $orders
         *
         * @see https://docs.backpack.exchange/#tag/Order/operation/execute_order_batch
         *
         * @param {Array} $orders list of $orders to create, each object should contain the parameters required by createOrder, namely symbol, $type, $side, $amount, $price and $params
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        $this->load_markets();
        $ordersRequests = array();
        for ($i = 0; $i < count($orders); $i++) {
            $rawOrder = $orders[$i];
            $marketId = $this->safe_string($rawOrder, 'symbol');
            $type = $this->safe_string($rawOrder, 'type');
            $side = $this->safe_string($rawOrder, 'side');
            $amount = $this->safe_number($rawOrder, 'amount');
            $price = $this->safe_number($rawOrder, 'price');
            $orderParams = $this->safe_dict($rawOrder, 'params', array());
            $extendedParams = $this->extend($orderParams, $params); // the request does not accept extra $params since it's a list, so we're extending each order with the common $params
            $orderRequest = $this->create_order_request($marketId, $type, $side, $amount, $price, $extendedParams);
            $ordersRequests[] = $orderRequest;
        }
        $response = $this->privatePostApiV1Orders ($ordersRequests);
        return $this->parse_orders($response);
    }

    public function create_order_request(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            'side' => $this->encode_order_side($side),
            'orderType' => $this->capitalize($type),
        );
        $triggerPrice = $this->safe_string($params, 'triggerPrice');
        $isTriggerOrder = $triggerPrice !== null;
        $quantityKey = $isTriggerOrder ? 'triggerQuantity' : 'quantity';
        // handle basic limit/market order types
        if ($type === 'limit') {
            $request['price'] = $this->price_to_precision($symbol, $price);
            $request[$quantityKey] = $this->amount_to_precision($symbol, $amount);
        } elseif ($type === 'market') {
            $cost = $this->safe_string_2($params, 'cost', 'quoteQuantity');
            if ($cost !== null) {
                $request['quoteQuantity'] = $this->cost_to_precision($symbol, $cost);
                $params = $this->omit($params, array( 'cost', 'quoteQuantity' ));
            } else {
                $request[$quantityKey] = $this->amount_to_precision($symbol, $amount);
            }
        }
        // trigger orders
        if ($isTriggerOrder) {
            $request['triggerPrice'] = $this->price_to_precision($symbol, $triggerPrice);
            $params = $this->omit($params, 'triggerPrice');
        }
        $clientOrderId = $this->safe_integer($params, 'clientOrderId'); // the exchange requires uint
        if ($clientOrderId !== null) {
            $request['clientId'] = $clientOrderId;
            $params = $this->omit($params, 'clientOrderId');
        }
        $postOnly = false;
        list($postOnly, $params) = $this->handle_post_only($type === 'market', false, $params);
        if ($postOnly) {
            $params['postOnly'] = true;
        }
        $takeProfit = $this->safe_dict($params, 'takeProfit');
        if ($takeProfit !== null) {
            $takeProfitTriggerPrice = $this->safe_string($takeProfit, 'triggerPrice');
            if ($takeProfitTriggerPrice !== null) {
                $request['takeProfitTriggerPrice'] = $this->price_to_precision($symbol, $takeProfitTriggerPrice);
            }
            $takeProfitPrice = $this->safe_string($takeProfit, 'price');
            if ($takeProfitPrice !== null) {
                $request['takeProfitLimitPrice'] = $this->price_to_precision($symbol, $takeProfitPrice);
            }
            $params = $this->omit($params, 'takeProfit');
        }
        $stopLoss = $this->safe_dict($params, 'stopLoss');
        if ($stopLoss !== null) {
            $stopLossTriggerPrice = $this->safe_string($stopLoss, 'triggerPrice');
            if ($stopLossTriggerPrice !== null) {
                $request['stopLossTriggerPrice'] = $this->price_to_precision($symbol, $stopLossTriggerPrice);
            }
            $stopLossPrice = $this->safe_string($stopLoss, 'price');
            if ($stopLossPrice !== null) {
                $request['stopLossLimitPrice'] = $this->price_to_precision($symbol, $stopLossPrice);
            }
            $params = $this->omit($params, 'stopLoss');
        }
        $selfTradePrevention = null;
        list($selfTradePrevention, $params) = $this->handle_option_and_params($params, 'createOrder', 'selfTradePrevention');
        if ($selfTradePrevention !== null) {
            if ($selfTradePrevention === 'EXPIRE_MAKER') {
                $request['selfTradePrevention'] = 'RejectMaker';
            } elseif ($selfTradePrevention === 'EXPIRE_TAKER') {
                $request['selfTradePrevention'] = 'RejectTaker';
            } elseif ($selfTradePrevention === 'EXPIRE_BOTH') {
                $request['selfTradePrevention'] = 'RejectBoth';
            }
        }
        return $this->extend($request, $params);
    }

    public function encode_order_side($side) {
        $sides = array(
            'buy' => 'Bid',
            'sell' => 'Ask',
        );
        return $this->safe_string($sides, $side, $side);
    }

    public function fetch_open_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch all unfilled currently open orders
         *
         * @see https://docs.backpack.exchange/#tag/Order/operation/get_open_orders
         *
         * @param {string} $symbol unified $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch open orders for
         * @param {int} [$limit] the maximum number of open orders structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        $request = array();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        $response = $this->privateGetApiV1Orders ($this->extend($request, $params));
        return $this->parse_orders($response, $market, $since, $limit);
    }

    public function fetch_open_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * fetch an open order by it's $id
         *
         * @see https://docs.backpack.exchange/#tag/Order/operation/get_order
         *
         * @param {string} $id order $id
         * @param {string} $symbol not used by hollaex fetchOpenOrder ()
         * @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();
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchOpenOrder() requires a $symbol argument');
        }
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            'orderId' => $id,
        );
        $response = $this->privateGetApiV1Order ($this->extend($request, $params));
        return $this->parse_order($response);
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * cancels an open order
         *
         * @see https://docs.backpack.exchange/#tag/Order/operation/cancel_order
         *
         * @param {string} $id order $id
         * @param {string} $symbol unified $symbol of the $market the order was made in
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $this->load_markets();
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' cancelOrder() requires a $symbol argument');
        }
        $market = $this->market($symbol);
        $request = array(
            'orderId' => $id,
            'symbol' => $market['id'],
        );
        $response = $this->privateDeleteApiV1Order ($this->extend($request, $params));
        return $this->parse_order($response);
    }

    public function cancel_all_orders(?string $symbol = null, $params = array ()) {
        /**
         * cancel all open orders
         *
         * @see https://docs.backpack.exchange/#tag/Order/operation/cancel_open_orders
         *
         * @param {string} $symbol unified $market $symbol, only orders in the $market of this $symbol are cancelled when $symbol is not null
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' cancelOrder() requires a $symbol argument');
        }
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = $this->privateDeleteApiV1Orders ($this->extend($request, $params));
        return $this->parse_orders($response, $market);
    }

    public function fetch_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on multiple orders made by the user
         *
         * @see https://docs.backpack.exchange/#tag/History/operation/get_order_history
         *
         * @param {string} $symbol unified $market $symbol of the $market orders were made in
         * @param {int} [$since] the earliest time in ms to fetch orders for
         * @param {int} [$limit] the maximum number of  orde structures to retrieve (default 100, max 1000)
         * @param {array} [$params] extra parameters specific to the bitteam api endpoint
         * @return {Order[]} a list of {@link https://github.com/ccxt/ccxt/wiki/Manual#order-structure order structures}
         */
        $this->load_markets();
        $request = array();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->privateGetWapiV1HistoryOrders ($this->extend($request, $params));
        return $this->parse_orders($response, $market, $since, $limit);
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        //     {
        //         "clientId" => null,
        //         "createdAt" => 1753624283415,
        //         "executedQuantity" => "0.001",
        //         "executedQuoteQuantity" => "3.81428",
        //         "id" => "4227701917",
        //         "orderType" => "Market",
        //         "quantity" => "0.001",
        //         "quoteQuantity" => "3.82",
        //         "reduceOnly" => null,
        //         "relatedOrderId" => null,
        //         "selfTradePrevention" => "RejectTaker",
        //         "side" => "Bid",
        //         "status" => "Filled",
        //         "stopLossLimitPrice" => null,
        //         "stopLossTriggerBy" => null,
        //         "stopLossTriggerPrice" => null,
        //         "strategyId" => null,
        //         "symbol" => "ETH_USDC",
        //         "takeProfitLimitPrice" => null,
        //         "takeProfitTriggerBy" => null,
        //         "takeProfitTriggerPrice" => null,
        //         "timeInForce" => "GTC",
        //         "triggerBy" => null,
        //         "triggerPrice" => null,
        //         "triggerQuantity" => null,
        //         "triggeredAt" => null
        //     }
        //
        // fetchOpenOrders
        //     {
        //         "clientId" => 123456789,
        //         "createdAt" => 1753626206762,
        //         "executedQuantity" => "0",
        //         "executedQuoteQuantity" => "0",
        //         "id" => "4228978331",
        //         "orderType" => "Limit",
        //         "postOnly" => true,
        //         "price" => "3000",
        //         "quantity" => "0.001",
        //         "reduceOnly" => null,
        //         "relatedOrderId" => null,
        //         "selfTradePrevention" => "RejectTaker",
        //         "side" => "Bid",
        //         "status" => "New",
        //         "stopLossLimitPrice" => null,
        //         "stopLossTriggerBy" => null,
        //         "stopLossTriggerPrice" => null,
        //         "strategyId" => null,
        //         "symbol" => "ETH_USDC",
        //         "takeProfitLimitPrice" => null,
        //         "takeProfitTriggerBy" => null,
        //         "takeProfitTriggerPrice" => null,
        //         "timeInForce" => "GTC",
        //         "triggerBy" => null,
        //         "triggerPrice" => null,
        //         "triggerQuantity" => null,
        //         "triggeredAt" => null
        //     }
        //
        // fetchOrders
        //     {
        //         "clientId" => null,
        //         "createdAt" => "2025-07-27T18:05:40.897",
        //         "executedQuantity" => "0",
        //         "executedQuoteQuantity" => "0",
        //         "expiryReason" => null,
        //         "id" => "4239996998",
        //         "orderType" => "Limit",
        //         "postOnly" => false,
        //         "price" => "4500",
        //         "quantity" => null,
        //         "quoteQuantity" => null,
        //         "selfTradePrevention" => "RejectTaker",
        //         "side" => "Ask",
        //         "status" => "Cancelled",
        //         "stopLossLimitPrice" => null,
        //         "stopLossTriggerBy" => null,
        //         "stopLossTriggerPrice" => null,
        //         "strategyId" => null,
        //         "symbol" => "ETH_USDC",
        //         "systemOrderType" => null,
        //         "takeProfitLimitPrice" => null,
        //         "takeProfitTriggerBy" => null,
        //         "takeProfitTriggerPrice" => null,
        //         "timeInForce" => "GTC",
        //         "triggerBy" => null,
        //         "triggerPrice" => "4300",
        //         "triggerQuantity" => "0.001"
        //     }
        //
        $timestamp = $this->safe_integer($order, 'createdAt');
        $timestamp2 = $this->parse8601($this->safe_string($order, 'createdAt'));
        if ($timestamp2 !== null) {
            $timestamp = $timestamp2;
        }
        $id = $this->safe_string($order, 'id');
        $clientOrderId = $this->safe_string($order, 'clientId');
        $symbol = $this->safe_symbol($this->safe_string($order, 'symbol'), $market);
        $type = $this->safe_string_lower($order, 'orderType');
        $timeInForce = $this->safe_string($order, 'timeInForce');
        $side = $this->parse_order_side($this->safe_string($order, 'side'));
        $amount = $this->safe_string_2($order, 'quantity', 'triggerQuantity');
        $price = $this->safe_string($order, 'price');
        $cost = $this->safe_string($order, 'executedQuoteQuantity');
        $status = $this->parse_order_status($this->safe_string($order, 'status'));
        $triggerPrice = $this->safe_string($order, 'triggerPrice');
        $filled = $this->safe_string($order, 'executedQuantity');
        $reduceOnly = $this->safe_bool($order, 'reduceOnly');
        $postOnly = $this->safe_bool($order, 'postOnly');
        $stopLossPrice = $this->safe_string_2($order, 'stopLossLimitPrice', 'stopLossTriggerPrice');
        $takeProfitPrice = $this->safe_string_2($order, 'takeProfitLimitPrice', 'takeProfitTriggerPrice');
        return $this->safe_order(array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => $clientOrderId,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => $timeInForce,
            'postOnly' => $postOnly,
            'reduceOnly' => $reduceOnly,
            'side' => $side,
            'price' => $price,
            'triggerPrice' => $triggerPrice,
            'stopLossPrice' => $stopLossPrice,
            'takeProfitPrice' => $takeProfitPrice,
            'amount' => $amount,
            'cost' => $cost,
            'average' => null,
            'filled' => $filled,
            'remaining' => null,
            'status' => $status,
            'fee' => null,
            'trades' => null,
        ), $market);
    }

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

    public function parse_order_side(?string $side) {
        $sides = array(
            'Bid' => 'buy',
            'Ask' => 'sell',
        );
        return $this->safe_string($sides, $side, $side);
    }

    public function fetch_positions(?array $symbols = null, $params = array ()): array {
        /**
         * fetch all open $positions
         *
         * @see https://docs.backpack.exchange/#tag/Futures/operation/get_positions
         *
         * @param {string[]|null} $symbols list of unified market $symbols
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=position-structure position structure~
         */
        $this->load_markets();
        $response = $this->privateGetApiV1Position ($params);
        $positions = $this->parse_positions($response);
        if ($this->is_empty($symbols)) {
            return $positions;
        }
        $symbols = $this->market_symbols($symbols);
        return $this->filter_by_array_positions($positions, 'symbol', $symbols, false);
    }

    public function parse_position(array $position, ?array $market = null) {
        //
        // fetchPositions
        //     {
        //         "breakEvenPrice" => "3831.3630555555555555555555556",
        //         "cumulativeFundingPayment" => "-0.009218",
        //         "cumulativeInterest" => "0",
        //         "entryPrice" => "3826.8888888888888888888888889",
        //         "estLiquidationPrice" => "0",
        //         "imf" => "0.02",
        //         "imfFunction" => array(
        //             "base" => "0.02",
        //             "factor" => "0.0000935",
        //             "type" => "sqrt"
        //         ),
        //         "markPrice" => "3787.46813304",
        //         "mmf" => "0.0125",
        //         "mmfFunction" => array(
        //             "base" => "0.0125",
        //             "factor" => "0.0000561",
        //             "type" => "sqrt"
        //         ),
        //         "netCost" => "13.7768",
        //         "netExposureNotional" => "13.634885278944",
        //         "netExposureQuantity" => "0.0036",
        //         "netQuantity" => "0.0036",
        //         "pnlRealized" => "0",
        //         "pnlUnrealized" => "-0.141914",
        //         "positionId" => "4238420454",
        //         "subaccountId" => null,
        //         "symbol" => "ETH_USDC_PERP",
        //         "userId":1813870
        //     }
        //
        //
        $id = $this->safe_string($position, 'positionId');
        $marketId = $this->safe_string($position, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $entryPrice = $this->safe_string($position, 'entryPrice');
        $markPrice = $this->safe_string($position, 'markPrice');
        $netCost = $this->safe_string($position, 'netCost');
        $hedged = false;
        $side = 'long';
        if (Precise::string_lt($netCost, '0')) {
            $side = 'short';
        }
        if ($netCost === null) {
            $hedged = null;
            $side = null;
        }
        $unrealizedPnl = $this->safe_string($position, 'pnlUnrealized');
        $realizedPnl = $this->safe_string($position, 'pnlRealized');
        $liquidationPrice = $this->safe_string($position, 'estLiquidationPrice');
        return $this->safe_position(array(
            'info' => $position,
            'id' => $id,
            'symbol' => $symbol,
            'timestamp' => $this->parse8601($this->safe_string($position, 'timestamp')),
            'datetime' => $this->iso8601($this->parse8601($this->safe_string($position, 'timestamp'))),
            'lastUpdateTimestamp' => null,
            'hedged' => $hedged,
            'side' => $side,
            'contracts' => $this->safe_string($position, 'netExposureQuantity'),
            'contractSize' => null,
            'entryPrice' => $entryPrice,
            'markPrice' => $markPrice,
            'lastPrice' => null,
            'notional' => Precise::string_abs($netCost),
            'leverage' => null,
            'collateral' => null,
            'initialMargin' => null,
            'initialMarginPercentage' => $this->safe_string($position, 'imf'),
            'maintenanceMargin' => null,
            'maintenanceMarginPercentage' => $this->safe_string($position, 'mmf'),
            'realizedPnl' => $realizedPnl,
            'unrealizedPnl' => $unrealizedPnl,
            'liquidationPrice' => $liquidationPrice,
            'marginMode' => null,
            'marginRatio' => null,
            'percentage' => null,
            'stopLossPrice' => null,
            'takeProfitPrice' => null,
        ));
    }

    public function fetch_funding_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetches the history of funding payments
         *
         * @see https://docs.backpack.exchange/#tag/History/operation/get_funding_payments
         *
         * @param {string} $symbol unified $symbol of the $market to fetch trades for
         * @param {int} [$since] timestamp in ms of the earliest trade to fetch (default 24 hours ago)
         * @param {int} [$limit] the maximum amount of trades to fetch (default 200, max 500)
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] timestamp in ms of the latest trade to fetch (default now)
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-trades trade structures~
         */
        $this->load_markets();
        $request = array();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->privateGetWapiV1HistoryFunding ($this->extend($request, $params));
        return $this->parse_incomes($response, $market, $since, $limit);
    }

    public function parse_income($income, ?array $market = null) {
        //
        //     {
        //         "fundingRate" => "0.0001",
        //         "intervalEndTimestamp" => "2025-08-01T16:00:00",
        //         "quantity" => "-0.001301",
        //         "subaccountId" => 0,
        //         "symbol" => "ETH_USDC_PERP",
        //         "userId" => 1813870
        //     }
        //
        $marketId = $this->safe_string($income, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $amount = $this->safe_number($income, 'quantity');
        $id = $this->safe_string($income, 'userId');
        $timestamp = $this->parse8601($this->safe_string($income, 'intervalEndTimestamp'));
        $rate = $this->safe_number($income, 'fundingRate');
        return array(
            'info' => $income,
            'symbol' => $symbol,
            'code' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'id' => $id,
            'amount' => $amount,
            'rate' => $rate,
        );
    }

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

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $endpoint = '/' . $path;
        $url = $this->urls['api'][$api];
        $sortedParams = gettype($params) === 'array' && array_keys($params) === array_keys(array_keys($params)) ? $params : $this->keysort($params);
        if ($api === 'private') {
            $this->check_required_credentials();
            $ts = (string) $this->nonce();
            $recvWindow = $this->safe_string_2($this->options, 'recvWindow', 'X-Window', '5000');
            $optionInstructions = $this->safe_dict($this->options, 'instructions', array());
            $optionPathInstructions = $this->safe_dict($optionInstructions, $path, array());
            $instruction = $this->safe_string($optionPathInstructions, $method, '');
            $payload = '';
            if (($path === 'api/v1/orders') && ($method === 'POST')) { // for createOrders
                $payload = $this->generate_batch_payload($sortedParams, $ts, $recvWindow, $instruction);
            } else {
                $queryString = $this->urlencode($sortedParams);
                if (strlen($queryString) > 0) {
                    $queryString .= '&';
                }
                $payload = 'instruction=' . $instruction . '&' . $queryString . 'timestamp=' . $ts . '&window=' . $recvWindow;
            }
            $secretBytes = base64_decode($this->secret);
            $seed = $this->array_slice($secretBytes, 0, 32);
            $signature = $this->eddsa($this->encode($payload), $seed, 'ed25519');
            $headers = array(
                'X-Timestamp' => $ts,
                'X-Window' => $recvWindow,
                'X-API-Key' => $this->apiKey,
                'X-Signature' => $signature,
                'X-Broker-Id' => '1400',
            );
            if ($method !== 'GET') {
                $body = $this->json($sortedParams);
                $headers['Content-Type'] = 'application/json';
            }
        }
        if ($method === 'GET') {
            $query = $this->urlencode($sortedParams);
            if (strlen($query) !== 0) {
                $endpoint .= '?' . $query;
            }
        }
        $url .= $endpoint;
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function generate_batch_payload($params, $ts, $recvWindow, $instruction) {
        $payload = '';
        for ($i = 0; $i < count($params); $i++) {
            $order = $this->safe_dict($params, $i, array());
            $sortedOrder = $this->keysort($order);
            $orderQuery = $this->urlencode($sortedOrder);
            $payload .= 'instruction=' . $instruction . '&' . $orderQuery . '&';
            if ($i === (strlen($params) - 1)) {
                $payload .= 'timestamp=' . $ts . '&window=' . $recvWindow;
            }
        }
        return $payload;
    }

    public function handle_errors(int $code, string $reason, string $url, string $method, array $headers, string $body, $response, $requestHeaders, $requestBody) {
        if ($response === null) {
            return null; // fallback to default error handler
        }
        //
        // array("code":"INVALID_ORDER","message":"Invalid order")
        // array("code":"INVALID_CLIENT_REQUEST","message":"Must specify both `triggerPrice` and `triggerQuantity` or neither")
        //
        $errorCode = $this->safe_string($response, 'code');
        $message = $this->safe_string($response, 'message');
        if ($errorCode !== null) {
            $feedback = $this->id . ' ' . $body;
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorCode, $feedback);
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $message, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $message, $feedback);
            throw new ExchangeError($feedback); // unknown $message
        }
        return null;
    }
}
