<?php

namespace ccxt\async;

// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
// https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code

use Exception; // a common import
use ccxt\async\abstract\bitmex as Exchange;
use ccxt\ExchangeError;
use ccxt\ArgumentsRequired;
use ccxt\BadRequest;
use ccxt\BadSymbol;
use ccxt\InvalidOrder;
use ccxt\OrderNotFound;
use ccxt\DDoSProtection;
use ccxt\Precise;
use \React\Async;
use \React\Promise\PromiseInterface;

class bitmex extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'bitmex',
            'name' => 'BitMEX',
            'countries' => array( 'SC' ), // Seychelles
            'version' => 'v1',
            'userAgent' => null,
            // cheapest endpoints are 10 requests per second (trading)
            // 10 per second => rateLimit = 1000ms / 10 = 100ms
            // 120 per minute => 2 per second => weight = 5 (authenticated)
            // 30 per minute => 0.5 per second => weight = 20 (unauthenticated)
            'rateLimit' => 100,
            'certified' => true,
            'pro' => true,
            'has' => array(
                'CORS' => null,
                'spot' => true,
                'margin' => false,
                'swap' => true,
                'future' => true,
                'option' => false,
                'addMargin' => null,
                'cancelAllOrders' => true,
                'cancelAllOrdersAfter' => true,
                'cancelOrder' => true,
                'cancelOrders' => true,
                'closeAllPositions' => false,
                'closePosition' => true,
                'createOrder' => true,
                'createReduceOnlyOrder' => true,
                'createStopOrder' => true,
                'createTrailingAmountOrder' => true,
                'createTriggerOrder' => true,
                'editOrder' => true,
                'fetchBalance' => true,
                'fetchClosedOrders' => true,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => true,
                'fetchDepositAddresses' => false,
                'fetchDepositAddressesByNetwork' => false,
                'fetchDepositsWithdrawals' => 'emulated',
                'fetchDepositWithdrawFee' => 'emulated',
                'fetchDepositWithdrawFees' => true,
                'fetchFundingHistory' => false,
                'fetchFundingRate' => 'emulated', // emulated in exchange
                'fetchFundingRateHistory' => true,
                'fetchFundingRates' => true,
                'fetchIndexOHLCV' => false,
                'fetchLedger' => true,
                'fetchLeverage' => 'emulated',
                'fetchLeverages' => true,
                'fetchLeverageTiers' => false,
                'fetchLiquidations' => true,
                'fetchMarginAdjustmentHistory' => false,
                'fetchMarketLeverageTiers' => false,
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => false,
                'fetchMyLiquidations' => false,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => true,
                'fetchPosition' => false,
                'fetchPositionHistory' => false,
                'fetchPositions' => true,
                'fetchPositionsHistory' => false,
                'fetchPositionsRisk' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTrades' => true,
                'fetchTransactions' => 'emulated',
                'fetchTransfer' => false,
                'fetchTransfers' => false,
                'index' => true,
                'reduceMargin' => null,
                'sandbox' => true,
                'setLeverage' => true,
                'setMargin' => null,
                'setMarginMode' => true,
                'setPositionMode' => false,
                'transfer' => false,
                'withdraw' => true,
            ),
            'timeframes' => array(
                '1m' => '1m',
                '5m' => '5m',
                '1h' => '1h',
                '1d' => '1d',
            ),
            'urls' => array(
                'test' => array(
                    'public' => 'https://testnet.bitmex.com',
                    'private' => 'https://testnet.bitmex.com',
                ),
                'logo' => 'https://github.com/user-attachments/assets/c78425ab-78d5-49d6-bd14-db7734798f04',
                'api' => array(
                    'public' => 'https://www.bitmex.com',
                    'private' => 'https://www.bitmex.com',
                ),
                'www' => 'https://www.bitmex.com',
                'doc' => array(
                    'https://www.bitmex.com/app/apiOverview',
                    'https://github.com/BitMEX/api-connectors/tree/master/official-http',
                ),
                'fees' => 'https://www.bitmex.com/app/fees',
                'referral' => array(
                    'url' => 'https://www.bitmex.com/app/register/NZTR1q',
                    'discount' => 0.1,
                ),
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'announcement' => 5,
                        'announcement/urgent' => 5,
                        'chat' => 5,
                        'chat/channels' => 5,
                        'chat/connected' => 5,
                        'chat/pinned' => 5,
                        'funding' => 5,
                        'guild' => 5,
                        'instrument' => 5,
                        'instrument/active' => 5,
                        'instrument/activeAndIndices' => 5,
                        'instrument/activeIntervals' => 5,
                        'instrument/compositeIndex' => 5,
                        'instrument/indices' => 5,
                        'instrument/usdVolume' => 5,
                        'insurance' => 5,
                        'leaderboard' => 5,
                        'liquidation' => 5,
                        'orderBook/L2' => 5,
                        'porl/nonce' => 5,
                        'quote' => 5,
                        'quote/bucketed' => 5,
                        'schema' => 5,
                        'schema/websocketHelp' => 5,
                        'settlement' => 5,
                        'stats' => 5,
                        'stats/history' => 5,
                        'stats/historyUSD' => 5,
                        'trade' => 5,
                        'trade/bucketed' => 5,
                        'wallet/assets' => 5,
                        'wallet/networks' => 5,
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'address' => 5,
                        'apiKey' => 5,
                        'execution' => 5,
                        'execution/tradeHistory' => 5,
                        'globalNotification' => 5,
                        'leaderboard/name' => 5,
                        'order' => 5,
                        'porl/snapshots' => 5,
                        'position' => 5,
                        'user' => 5,
                        'user/affiliateStatus' => 5,
                        'user/checkReferralCode' => 5,
                        'user/commission' => 5,
                        'user/csa' => 5,
                        'user/depositAddress' => 5,
                        'user/executionHistory' => 5,
                        'user/getWalletTransferAccounts' => 5,
                        'user/margin' => 5,
                        'user/quoteFillRatio' => 5,
                        'user/quoteValueRatio' => 5,
                        'user/staking' => 5,
                        'user/staking/instruments' => 5,
                        'user/staking/tiers' => 5,
                        'user/tradingVolume' => 5,
                        'user/unstakingRequests' => 5,
                        'user/wallet' => 5,
                        'user/walletHistory' => 5,
                        'user/walletSummary' => 5,
                        'userAffiliates' => 5,
                        'userEvent' => 5,
                    ),
                    'post' => array(
                        'address' => 5,
                        'chat' => 5,
                        'guild' => 5,
                        'guild/archive' => 5,
                        'guild/join' => 5,
                        'guild/kick' => 5,
                        'guild/leave' => 5,
                        'guild/sharesTrades' => 5,
                        'order' => 1,
                        'order/cancelAllAfter' => 5,
                        'order/closePosition' => 5,
                        'position/isolate' => 1,
                        'position/leverage' => 1,
                        'position/riskLimit' => 5,
                        'position/transferMargin' => 1,
                        'user/addSubaccount' => 5,
                        'user/cancelWithdrawal' => 5,
                        'user/communicationToken' => 5,
                        'user/confirmEmail' => 5,
                        'user/confirmWithdrawal' => 5,
                        'user/logout' => 5,
                        'user/preferences' => 5,
                        'user/requestWithdrawal' => 5,
                        'user/unstakingRequests' => 5,
                        'user/updateSubaccount' => 5,
                        'user/walletTransfer' => 5,
                    ),
                    'put' => array(
                        'guild' => 5,
                        'order' => 1,
                    ),
                    'delete' => array(
                        'order' => 1,
                        'order/all' => 1,
                        'user/unstakingRequests' => 5,
                    ),
                ),
            ),
            'exceptions' => array(
                'exact' => array(
                    'Invalid API Key.' => '\\ccxt\\AuthenticationError',
                    'This key is disabled.' => '\\ccxt\\PermissionDenied',
                    'Access Denied' => '\\ccxt\\PermissionDenied',
                    'Duplicate clOrdID' => '\\ccxt\\InvalidOrder',
                    'orderQty is invalid' => '\\ccxt\\InvalidOrder',
                    'Invalid price' => '\\ccxt\\InvalidOrder',
                    'Invalid stopPx for ordType' => '\\ccxt\\InvalidOrder',
                    'Account is restricted' => '\\ccxt\\PermissionDenied', // array("error":array("message":"Account is restricted","name":"HTTPError"))
                ),
                'broad' => array(
                    'Signature not valid' => '\\ccxt\\AuthenticationError',
                    'overloaded' => '\\ccxt\\ExchangeNotAvailable',
                    'Account has insufficient Available Balance' => '\\ccxt\\InsufficientFunds',
                    'Service unavailable' => '\\ccxt\\ExchangeNotAvailable', // array("error":array("message":"Service unavailable","name":"HTTPError"))
                    'Server Error' => '\\ccxt\\ExchangeError', // array("error":array("message":"Server Error","name":"HTTPError"))
                    'Unable to cancel order due to existing state' => '\\ccxt\\InvalidOrder',
                    'We require all new traders to verify' => '\\ccxt\\PermissionDenied', // array("message":"We require all new traders to verify their identity before their first deposit. Please visit bitmex.com/verify to complete the process.","name":"HTTPError")
                ),
            ),
            'precisionMode' => TICK_SIZE,
            'options' => array(
                // https://blog.bitmex.com/api_announcement/deprecation-of-api-nonce-header/
                // https://github.com/ccxt/ccxt/issues/4789
                'api-expires' => 5, // in seconds
                'fetchOHLCVOpenTimestamp' => true,
                'oldPrecision' => false,
                'networks' => array(
                    'BTC' => 'btc',
                    'ERC20' => 'eth',
                    'BEP20' => 'bsc',
                    'TRC20' => 'tron',
                    'AVAXC' => 'avax',
                    'NEAR' => 'near',
                    'XTZ' => 'xtz',
                    'DOT' => 'dot',
                    'SOL' => 'sol',
                    'ADA' => 'ada',
                ),
            ),
            'features' => array(
                'default' => array(
                    'sandbox' => true,
                    'createOrder' => array(
                        'marginMode' => true,
                        'triggerPrice' => true,
                        'triggerPriceType' => array(
                            'last' => true,
                            'mark' => true,
                        ),
                        'triggerDirection' => true,
                        'stopLossPrice' => false,
                        'takeProfitPrice' => false,
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => true,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'trailing' => true,
                        'marketBuyRequiresPrice' => false,
                        'marketBuyByCost' => false,
                        // exchange-supported features
                        // 'selfTradePrevention' => true,
                        // 'twap' => false,
                        // 'iceberg' => false,
                        // 'oco' => false,
                    ),
                    'createOrders' => null,
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 500,
                        'daysBack' => null,
                        'untilDays' => 1000000,
                        'symbolRequired' => false,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => 500,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => array(
                        'marginMode' => false,
                        'limit' => 500,
                        'daysBack' => null,
                        'untilDays' => 1000000,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchClosedOrders' => array(
                        'marginMode' => false,
                        'limit' => 500,
                        'daysBack' => null,
                        'daysBackCanceled' => null,
                        'untilDays' => 1000000,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOHLCV' => array(
                        'limit' => 10000,
                    ),
                ),
                'spot' => array(
                    'extends' => 'default',
                    'createOrder' => array(
                        'triggerPriceType' => array(
                            'index' => false,
                        ),
                    ),
                ),
                'derivatives' => array(
                    'extends' => 'default',
                    'createOrder' => array(
                        'triggerPriceType' => array(
                            'index' => true,
                        ),
                    ),
                ),
                'swap' => array(
                    'linear' => array(
                        'extends' => 'derivatives',
                    ),
                    'inverse' => array(
                        'extends' => 'derivatives',
                    ),
                ),
                'future' => array(
                    'linear' => array(
                        'extends' => 'derivatives',
                    ),
                    'inverse' => array(
                        'extends' => 'derivatives',
                    ),
                ),
            ),
            'commonCurrencies' => array(
                'USDt' => 'USDT',
                'XBt' => 'BTC',
                'XBT' => 'BTC',
                'Gwei' => 'ETH',
                'GWEI' => 'ETH',
                'LAMP' => 'SOL',
                'LAMp' => 'SOL',
            ),
        ));
    }

    public function fetch_currencies($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetches all available currencies on an exchange
             *
             * @see https://www.bitmex.com/api/explorer/#!/Wallet/Wallet_getAssetsConfig
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an associative dictionary of currencies
             */
            $response = Async\await($this->publicGetWalletAssets ($params));
            //
            //    {
            //        "XBt" => {
            //            "asset" => "XBT",
            //            "currency" => "XBt",
            //            "majorCurrency" => "XBT",
            //            "name" => "Bitcoin",
            //            "currencyType" => "Crypto",
            //            "scale" => "8",
            //            // "mediumPrecision" => "8",
            //            // "shorterPrecision" => "4",
            //            // "symbol" => "₿",
            //            // "tickLog" => "0",
            //            // "weight" => "1",
            //            "enabled" => true,
            //            "isMarginCurrency" => true,
            //            "minDepositAmount" => "10000",
            //            "minWithdrawalAmount" => "1000",
            //            "maxWithdrawalAmount" => "100000000000000",
            //            "networks" => array(
            //                array(
            //                    "asset" => "btc",
            //                    "tokenAddress" => "",
            //                    "depositEnabled" => true,
            //                    "withdrawalEnabled" => true,
            //                    "withdrawalFee" => "20000",
            //                    "minFee" => "20000",
            //                    "maxFee" => "10000000"
            //                }
            //            )
            //        ),
            //     }
            //
            $result = array();
            for ($i = 0; $i < count($response); $i++) {
                $currency = $response[$i];
                $asset = $this->safe_string($currency, 'asset');
                $code = $this->safe_currency_code($asset);
                $id = $this->safe_string($currency, 'currency');
                $name = $this->safe_string($currency, 'name');
                $chains = $this->safe_value($currency, 'networks', array());
                $depositEnabled = false;
                $withdrawEnabled = false;
                $networks = array();
                $scale = $this->safe_string($currency, 'scale');
                $precisionString = $this->parse_precision($scale);
                $precision = $this->parse_number($precisionString);
                for ($j = 0; $j < count($chains); $j++) {
                    $chain = $chains[$j];
                    $networkId = $this->safe_string($chain, 'asset');
                    $network = $this->network_id_to_code($networkId);
                    $withdrawalFeeRaw = $this->safe_string($chain, 'withdrawalFee');
                    $withdrawalFee = $this->parse_number(Precise::string_mul($withdrawalFeeRaw, $precisionString));
                    $isDepositEnabled = $this->safe_bool($chain, 'depositEnabled', false);
                    $isWithdrawEnabled = $this->safe_bool($chain, 'withdrawalEnabled', false);
                    $active = ($isDepositEnabled && $isWithdrawEnabled);
                    if ($isDepositEnabled) {
                        $depositEnabled = true;
                    }
                    if ($isWithdrawEnabled) {
                        $withdrawEnabled = true;
                    }
                    $networks[$network] = array(
                        'info' => $chain,
                        'id' => $networkId,
                        'network' => $network,
                        'active' => $active,
                        'deposit' => $isDepositEnabled,
                        'withdraw' => $isWithdrawEnabled,
                        'fee' => $withdrawalFee,
                        'precision' => null,
                        'limits' => array(
                            'withdraw' => array(
                                'min' => null,
                                'max' => null,
                            ),
                            'deposit' => array(
                                'min' => null,
                                'max' => null,
                            ),
                        ),
                    );
                }
                $currencyEnabled = $this->safe_value($currency, 'enabled');
                $currencyActive = $currencyEnabled || ($depositEnabled || $withdrawEnabled);
                $minWithdrawalString = $this->safe_string($currency, 'minWithdrawalAmount');
                $minWithdrawal = $this->parse_number(Precise::string_mul($minWithdrawalString, $precisionString));
                $maxWithdrawalString = $this->safe_string($currency, 'maxWithdrawalAmount');
                $maxWithdrawal = $this->parse_number(Precise::string_mul($maxWithdrawalString, $precisionString));
                $minDepositString = $this->safe_string($currency, 'minDepositAmount');
                $minDeposit = $this->parse_number(Precise::string_mul($minDepositString, $precisionString));
                $isCrypto = $this->safe_string($currency, 'currencyType') === 'Crypto';
                $result[$code] = array(
                    'id' => $id,
                    'code' => $code,
                    'info' => $currency,
                    'name' => $name,
                    'active' => $currencyActive,
                    'deposit' => $depositEnabled,
                    'withdraw' => $withdrawEnabled,
                    'fee' => null,
                    'precision' => $precision,
                    'limits' => array(
                        'amount' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'withdraw' => array(
                            'min' => $minWithdrawal,
                            'max' => $maxWithdrawal,
                        ),
                        'deposit' => array(
                            'min' => $minDeposit,
                            'max' => null,
                        ),
                    ),
                    'networks' => $networks,
                    'type' => $isCrypto ? 'crypto' : 'other',
                );
            }
            return $result;
        }) ();
    }

    public function convert_from_real_amount($code, $amount) {
        $currency = $this->currency($code);
        $precision = $this->safe_string($currency, 'precision');
        $amountString = $this->number_to_string($amount);
        $finalAmount = Precise::string_div($amountString, $precision);
        return $this->parse_number($finalAmount);
    }

    public function convert_to_real_amount(?string $code, ?string $amount) {
        if ($code === null) {
            return $amount;
        } elseif ($amount === null) {
            return null;
        }
        $currency = $this->currency($code);
        $precision = $this->safe_string($currency, 'precision');
        return Precise::string_mul($amount, $precision);
    }

    public function amount_to_precision($symbol, $amount) {
        $symbol = $this->safe_symbol($symbol);
        $market = $this->market($symbol);
        $oldPrecision = $this->safe_value($this->options, 'oldPrecision');
        if ($market['spot'] && !$oldPrecision) {
            $amount = $this->convert_from_real_amount($market['base'], $amount);
        }
        return parent::amount_to_precision($symbol, $amount);
    }

    public function convert_from_raw_quantity($symbol, $rawQuantity, $currencySide = 'base') {
        if ($this->safe_value($this->options, 'oldPrecision')) {
            return $this->parse_number($rawQuantity);
        }
        $symbol = $this->safe_symbol($symbol);
        $marketExists = $this->in_array($symbol, $this->symbols);
        if (!$marketExists) {
            return $this->parse_number($rawQuantity);
        }
        $market = $this->market($symbol);
        if ($market['spot']) {
            return $this->parse_number($this->convert_to_real_amount($market[$currencySide], $rawQuantity));
        }
        return $this->parse_number($rawQuantity);
    }

    public function convert_from_raw_cost($symbol, $rawQuantity) {
        return $this->convert_from_raw_quantity($symbol, $rawQuantity, 'quote');
    }

    public function fetch_markets($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * retrieves data on all markets for bitmex
             *
             * @see https://www.bitmex.com/api/explorer/#!/Instrument/Instrument_getActive
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} an array of objects representing market data
             */
            $response = Async\await($this->publicGetInstrumentActive ($params));
            //
            //  array(
            //    {
            //        "symbol" => "LTCUSDT",
            //        "rootSymbol" => "LTC",
            //        "state" => "Open",
            //        "typ" => "FFWCSX",
            //        "listing" => "2021-11-10T04:00:00.000Z",
            //        "front" => "2021-11-10T04:00:00.000Z",
            //        "expiry" => null,
            //        "settle" => null,
            //        "listedSettle" => null,
            //        "relistInterval" => null,
            //        "inverseLeg" => "",
            //        "sellLeg" => "",
            //        "buyLeg" => "",
            //        "optionStrikePcnt" => null,
            //        "optionStrikeRound" => null,
            //        "optionStrikePrice" => null,
            //        "optionMultiplier" => null,
            //        "positionCurrency" => "LTC", // can be empty for spot markets
            //        "underlying" => "LTC",
            //        "quoteCurrency" => "USDT",
            //        "underlyingSymbol" => "LTCT=", // can be empty for spot markets
            //        "reference" => "BMEX",
            //        "referenceSymbol" => ".BLTCT", // can be empty for spot markets
            //        "calcInterval" => null,
            //        "publishInterval" => null,
            //        "publishTime" => null,
            //        "maxOrderQty" => 1000000000,
            //        "maxPrice" => 1000000,
            //        "lotSize" => 1000,
            //        "tickSize" => 0.01,
            //        "multiplier" => 100,
            //        "settlCurrency" => "USDt", // can be empty for spot markets
            //        "underlyingToPositionMultiplier" => 10000,
            //        "underlyingToSettleMultiplier" => null,
            //        "quoteToSettleMultiplier" => 1000000,
            //        "isQuanto" => false,
            //        "isInverse" => false,
            //        "initMargin" => 0.03,
            //        "maintMargin" => 0.015,
            //        "riskLimit" => 1000000000000, // can be null for spot markets
            //        "riskStep" => 1000000000000, // can be null for spot markets
            //        "limit" => null,
            //        "capped" => false,
            //        "taxed" => true,
            //        "deleverage" => true,
            //        "makerFee" => -0.0001,
            //        "takerFee" => 0.0005,
            //        "settlementFee" => 0,
            //        "insuranceFee" => 0,
            //        "fundingBaseSymbol" => ".LTCBON8H", // can be empty for spot markets
            //        "fundingQuoteSymbol" => ".USDTBON8H", // can be empty for spot markets
            //        "fundingPremiumSymbol" => ".LTCUSDTPI8H", // can be empty for spot markets
            //        "fundingTimestamp" => "2022-01-14T20:00:00.000Z",
            //        "fundingInterval" => "2000-01-01T08:00:00.000Z",
            //        "fundingRate" => 0.0001,
            //        "indicativeFundingRate" => 0.0001,
            //        "rebalanceTimestamp" => null,
            //        "rebalanceInterval" => null,
            //        "openingTimestamp" => "2022-01-14T17:00:00.000Z",
            //        "closingTimestamp" => "2022-01-14T18:00:00.000Z",
            //        "sessionInterval" => "2000-01-01T01:00:00.000Z",
            //        "prevClosePrice" => 138.511,
            //        "limitDownPrice" => null,
            //        "limitUpPrice" => null,
            //        "bankruptLimitDownPrice" => null,
            //        "bankruptLimitUpPrice" => null,
            //        "prevTotalVolume" => 12699024000,
            //        "totalVolume" => 12702160000,
            //        "volume" => 3136000,
            //        "volume24h" => 114251000,
            //        "prevTotalTurnover" => 232418052349000,
            //        "totalTurnover" => 232463353260000,
            //        "turnover" => 45300911000,
            //        "turnover24h" => 1604331340000,
            //        "homeNotional24h" => 11425.1,
            //        "foreignNotional24h" => 1604331.3400000003,
            //        "prevPrice24h" => 135.48,
            //        "vwap" => 140.42165,
            //        "highPrice" => 146.42,
            //        "lowPrice" => 135.08,
            //        "lastPrice" => 144.36,
            //        "lastPriceProtected" => 144.36,
            //        "lastTickDirection" => "MinusTick",
            //        "lastChangePcnt" => 0.0655,
            //        "bidPrice" => 143.75,
            //        "midPrice" => 143.855,
            //        "askPrice" => 143.96,
            //        "impactBidPrice" => 143.75,
            //        "impactMidPrice" => 143.855,
            //        "impactAskPrice" => 143.96,
            //        "hasLiquidity" => true,
            //        "openInterest" => 38103000,
            //        "openValue" => 547963053300,
            //        "fairMethod" => "FundingRate",
            //        "fairBasisRate" => 0.1095,
            //        "fairBasis" => 0.004,
            //        "fairPrice" => 143.811,
            //        "markMethod" => "FairPrice",
            //        "markPrice" => 143.811,
            //        "indicativeTaxRate" => null,
            //        "indicativeSettlePrice" => 143.807,
            //        "optionUnderlyingPrice" => null,
            //        "settledPriceAdjustmentRate" => null,
            //        "settledPrice" => null,
            //        "timestamp" => "2022-01-14T17:49:55.000Z"
            //    }
            //  )
            //
            return $this->parse_markets($response);
        }) ();
    }

    public function parse_market(array $market): array {
        $id = $this->safe_string($market, 'symbol');
        $baseId = $this->safe_string($market, 'underlying');
        $quoteId = $this->safe_string($market, 'quoteCurrency');
        $settleId = $this->safe_string($market, 'settlCurrency');
        $settle = $this->safe_currency_code($settleId);
        // 'positionCurrency' may be empty ("", currently returns for ETHUSD)
        // so let's take the settlCurrency first and then adjust if needed
        $typ = $this->safe_string($market, 'typ'); // $type definitions at => https://www.bitmex.com/api/explorer/#!/Instrument/Instrument_get
        $swap = false;
        $spot = false;
        $future = false;
        if ($typ === 'FFWCSX') {
            $type = 'swap';
            $swap = true;
        } elseif ($typ === 'IFXXXP') {
            $type = 'spot';
            $spot = true;
        } elseif ($typ === 'FFCCSX') {
            $type = 'future';
            $future = true;
        } elseif ($typ === 'FFICSX') {
            // prediction markets (without any volume)
            $quoteId = $baseId;
            $baseId = $this->safe_string($market, 'rootSymbol');
            $type = 'future';
            $future = true;
        }
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $contract = $swap || $future;
        $contractSize = null;
        $isInverse = $this->safe_value($market, 'isInverse');  // this is true when BASE and SETTLE are same, i.e. BTC/XXX:BTC
        $isQuanto = $this->safe_value($market, 'isQuanto'); // this is true when BASE and SETTLE are different, i.e. AXS/XXX:BTC
        $linear = $contract ? (!$isInverse && !$isQuanto) : null;
        $status = $this->safe_string($market, 'state');
        $active = $status === 'Open'; // Open, Settled, Unlisted
        $expiry = null;
        $expiryDatetime = null;
        $symbol = null;
        if ($spot) {
            $symbol = $base . '/' . $quote;
        } elseif ($contract) {
            $symbol = $base . '/' . $quote . ':' . $settle;
            if ($linear) {
                $multiplierString = $this->safe_string_2($market, 'underlyingToPositionMultiplier', 'underlyingToSettleMultiplier');
                $contractSize = $this->parse_number(Precise::string_div('1', $multiplierString));
            } else {
                $multiplierString = Precise::string_abs($this->safe_string($market, 'multiplier'));
                $contractSize = $this->parse_number($multiplierString);
            }
            $expiryDatetime = $this->safe_string($market, 'expiry');
            $expiry = $this->parse8601($expiryDatetime);
            if ($expiry !== null) {
                $symbol = $symbol . '-' . $this->yymmdd($expiry);
            }
        } else {
            // for index/exotic markets, default to $id
            $symbol = $id;
        }
        $positionId = $this->safe_string_2($market, 'positionCurrency', 'underlying');
        $position = $this->safe_currency_code($positionId);
        $positionIsQuote = ($position === $quote);
        $maxOrderQty = $this->safe_number($market, 'maxOrderQty');
        $initMargin = $this->safe_string($market, 'initMargin', '1');
        $maxLeverage = $this->parse_number(Precise::string_div('1', $initMargin));
        // subtype should be null for $spot markets
        if ($spot) {
            $isInverse = null;
            $isQuanto = null;
            $linear = null;
        }
        return array(
            'id' => $id,
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'settle' => $settle,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'settleId' => $settleId,
            'type' => $type,
            'spot' => $spot,
            'margin' => false,
            'swap' => $swap,
            'future' => $future,
            'option' => false,
            'active' => $active,
            'contract' => $contract,
            'linear' => $linear,
            'inverse' => $isInverse,
            'quanto' => $isQuanto,
            'taker' => $this->safe_number($market, 'takerFee'),
            'maker' => $this->safe_number($market, 'makerFee'),
            'contractSize' => $contractSize,
            'expiry' => $expiry,
            'expiryDatetime' => $expiryDatetime,
            'strike' => $this->safe_number($market, 'optionStrikePrice'),
            'optionType' => null,
            'precision' => array(
                'amount' => $this->safe_number($market, 'lotSize'),
                'price' => $this->safe_number($market, 'tickSize'),
            ),
            'limits' => array(
                'leverage' => array(
                    'min' => $contract ? $this->parse_number('1') : null,
                    'max' => $contract ? $maxLeverage : null,
                ),
                'amount' => array(
                    'min' => null,
                    'max' => $positionIsQuote ? null : $maxOrderQty,
                ),
                'price' => array(
                    'min' => null,
                    'max' => $this->safe_number($market, 'maxPrice'),
                ),
                'cost' => array(
                    'min' => null,
                    'max' => $positionIsQuote ? $maxOrderQty : null,
                ),
            ),
            'created' => null, // 'listing' field is buggy, e.g. 2200-02-01T00:00:00.000Z
            'info' => $market,
        );
    }

    public function parse_balance($response): array {
        //
        //     array(
        //         {
        //             "account":1455728,
        //             "currency":"XBt",
        //             "riskLimit":1000000000000,
        //             "prevState":"",
        //             "state":"",
        //             "action":"",
        //             "amount":263542,
        //             "pendingCredit":0,
        //             "pendingDebit":0,
        //             "confirmedDebit":0,
        //             "prevRealisedPnl":0,
        //             "prevUnrealisedPnl":0,
        //             "grossComm":0,
        //             "grossOpenCost":0,
        //             "grossOpenPremium":0,
        //             "grossExecCost":0,
        //             "grossMarkValue":0,
        //             "riskValue":0,
        //             "taxableMargin":0,
        //             "initMargin":0,
        //             "maintMargin":0,
        //             "sessionMargin":0,
        //             "targetExcessMargin":0,
        //             "varMargin":0,
        //             "realisedPnl":0,
        //             "unrealisedPnl":0,
        //             "indicativeTax":0,
        //             "unrealisedProfit":0,
        //             "syntheticMargin":null,
        //             "walletBalance":263542,
        //             "marginBalance":263542,
        //             "marginBalancePcnt":1,
        //             "marginLeverage":0,
        //             "marginUsedPcnt":0,
        //             "excessMargin":263542,
        //             "excessMarginPcnt":1,
        //             "availableMargin":263542,
        //             "withdrawableMargin":263542,
        //             "timestamp":"2020-08-03T12:01:01.246Z",
        //             "grossLastValue":0,
        //             "commission":null
        //         }
        //     )
        //
        $result = array( 'info' => $response );
        for ($i = 0; $i < count($response); $i++) {
            $balance = $response[$i];
            $currencyId = $this->safe_string($balance, 'currency');
            $code = $this->safe_currency_code($currencyId);
            $account = $this->account();
            $free = $this->safe_string($balance, 'availableMargin');
            $total = $this->safe_string($balance, 'marginBalance');
            $account['free'] = $this->convert_to_real_amount($code, $free);
            $account['total'] = $this->convert_to_real_amount($code, $total);
            $result[$code] = $account;
        }
        return $this->safe_balance($result);
    }

    public function fetch_balance($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * query for balance and get the amount of funds available for trading or funds locked in orders
             *
             * @see https://www.bitmex.com/api/explorer/#!/User/User_getMargin
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
             */
            Async\await($this->load_markets());
            $request = array(
                'currency' => 'all',
            );
            $response = Async\await($this->privateGetUserMargin ($this->extend($request, $params)));
            //
            //     array(
            //         {
            //             "account":1455728,
            //             "currency":"XBt",
            //             "riskLimit":1000000000000,
            //             "prevState":"",
            //             "state":"",
            //             "action":"",
            //             "amount":263542,
            //             "pendingCredit":0,
            //             "pendingDebit":0,
            //             "confirmedDebit":0,
            //             "prevRealisedPnl":0,
            //             "prevUnrealisedPnl":0,
            //             "grossComm":0,
            //             "grossOpenCost":0,
            //             "grossOpenPremium":0,
            //             "grossExecCost":0,
            //             "grossMarkValue":0,
            //             "riskValue":0,
            //             "taxableMargin":0,
            //             "initMargin":0,
            //             "maintMargin":0,
            //             "sessionMargin":0,
            //             "targetExcessMargin":0,
            //             "varMargin":0,
            //             "realisedPnl":0,
            //             "unrealisedPnl":0,
            //             "indicativeTax":0,
            //             "unrealisedProfit":0,
            //             "syntheticMargin":null,
            //             "walletBalance":263542,
            //             "marginBalance":263542,
            //             "marginBalancePcnt":1,
            //             "marginLeverage":0,
            //             "marginUsedPcnt":0,
            //             "excessMargin":263542,
            //             "excessMarginPcnt":1,
            //             "availableMargin":263542,
            //             "withdrawableMargin":263542,
            //             "timestamp":"2020-08-03T12:01:01.246Z",
            //             "grossLastValue":0,
            //             "commission":null
            //         }
            //     )
            //
            return $this->parse_balance($response);
        }) ();
    }

    public function fetch_order_book(string $symbol, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $limit, $params) {
            /**
             * fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
             *
             * @see https://www.bitmex.com/api/explorer/#!/OrderBook/OrderBook_getL2
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the $order book for
             * @param {int} [$limit] the maximum $amount of $order book entries to return
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} A dictionary of ~@link https://docs.ccxt.com/#/?id=$order-book-structure $order book structures~ indexed by $market symbols
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
            );
            if ($limit !== null) {
                $request['depth'] = $limit;
            }
            $response = Async\await($this->publicGetOrderBookL2 ($this->extend($request, $params)));
            $result = array(
                'symbol' => $symbol,
                'bids' => array(),
                'asks' => array(),
                'timestamp' => null,
                'datetime' => null,
                'nonce' => null,
            );
            for ($i = 0; $i < count($response); $i++) {
                $order = $response[$i];
                $side = ($order['side'] === 'Sell') ? 'asks' : 'bids';
                $amount = $this->convert_from_raw_quantity($symbol, $this->safe_string($order, 'size'));
                $price = $this->safe_number($order, 'price');
                // https://github.com/ccxt/ccxt/issues/4926
                // https://github.com/ccxt/ccxt/issues/4927
                // the exchange sometimes returns null $price in the orderbook
                if ($price !== null) {
                    $resultSide = $result[$side];
                    $resultSide[] = array( $price, $amount );
                }
            }
            $result['bids'] = $this->sort_by($result['bids'], 0, true);
            $result['asks'] = $this->sort_by($result['asks'], 0);
            return $result;
        }) ();
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $params) {
            /**
             * fetches information on an order made by the user
             *
             * @see https://www.bitmex.com/api/explorer/#!/Order/Order_getOrders
             *
             * @param {string} $id the order $id
             * @param {string} $symbol unified $symbol of the market the order was made in
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
             */
            $filter = array(
                'filter' => array(
                    'orderID' => $id,
                ),
            );
            $response = Async\await($this->fetch_orders($symbol, null, null, $this->deep_extend($filter, $params)));
            $numResults = count($response);
            if ($numResults === 1) {
                return $response[0];
            }
            throw new OrderNotFound($this->id . ' => The order ' . $id . ' not found.');
        }) ();
    }

    public function fetch_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             *
             * @see https://www.bitmex.com/api/explorer/#!/Order/Order_getOrders
             *
             * fetches information on multiple orders made by the user
             * @param {string} $symbol unified $market $symbol of the $market orders were made in
             * @param {int} [$since] the earliest time in ms to fetch orders for
             * @param {int} [$limit] the maximum number of order structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] the earliest time in ms to fetch orders for
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchOrders', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_dynamic('fetchOrders', $symbol, $since, $limit, $params, 100));
            }
            $market = null;
            $request = array();
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['symbol'] = $market['id'];
            }
            if ($since !== null) {
                $request['startTime'] = $this->iso8601($since);
            }
            if ($limit !== null) {
                $request['count'] = $limit;
            }
            $until = $this->safe_integer_2($params, 'until', 'endTime');
            if ($until !== null) {
                $params = $this->omit($params, array( 'until' ));
                $request['endTime'] = $this->iso8601($until);
            }
            $request = $this->deep_extend($request, $params);
            // why the hassle? urlencode in python is kinda broken for nested dicts.
            // E.g. self.urlencode(array("filter" => array("open" => True))) will return "filter=array('open':+True)"
            // Bitmex doesn't like that. Hence resorting to this hack.
            if (is_array($request) && array_key_exists('filter', $request)) {
                $request['filter'] = $this->json($request['filter']);
            }
            $response = Async\await($this->privateGetOrder ($request));
            return $this->parse_orders($response, $market, $since, $limit);
        }) ();
    }

    public function fetch_open_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetch all unfilled currently open orders
             *
             * @see https://www.bitmex.com/api/explorer/#!/Order/Order_getOrders
             *
             * @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~
             */
            $request = array(
                'filter' => array(
                    'open' => true,
                ),
            );
            return Async\await($this->fetch_orders($symbol, $since, $limit, $this->deep_extend($request, $params)));
        }) ();
    }

    public function fetch_closed_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetches information on multiple closed $orders made by the user
             *
             * @see https://www.bitmex.com/api/explorer/#!/Order/Order_getOrders
             *
             * @param {string} $symbol unified market $symbol of the market $orders were made in
             * @param {int} [$since] the earliest time in ms to fetch $orders for
             * @param {int} [$limit] the maximum number of order structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            // Bitmex barfs if you set 'open' => false in the filter...
            $orders = Async\await($this->fetch_orders($symbol, $since, $limit, $params));
            return $this->filter_by_array($orders, 'status', array( 'closed', 'canceled' ), false);
        }) ();
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetch all trades made by the user
             *
             * @see https://www.bitmex.com/api/explorer/#!/Execution/Execution_getTradeHistory
             *
             * @param {string} $symbol unified $market $symbol
             * @param {int} [$since] the earliest time in ms to fetch trades for
             * @param {int} [$limit] the maximum number of trades structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchMyTrades', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_dynamic('fetchMyTrades', $symbol, $since, $limit, $params, 100));
            }
            $market = null;
            $request = array();
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['symbol'] = $market['id'];
            }
            if ($since !== null) {
                $request['startTime'] = $this->iso8601($since);
            }
            if ($limit !== null) {
                $request['count'] = min (500, $limit);
            }
            $until = $this->safe_integer_2($params, 'until', 'endTime');
            if ($until !== null) {
                $params = $this->omit($params, array( 'until' ));
                $request['endTime'] = $this->iso8601($until);
            }
            $request = $this->deep_extend($request, $params);
            // why the hassle? urlencode in python is kinda broken for nested dicts.
            // E.g. self.urlencode(array("filter" => array("open" => True))) will return "filter=array('open':+True)"
            // Bitmex doesn't like that. Hence resorting to this hack.
            if (is_array($request) && array_key_exists('filter', $request)) {
                $request['filter'] = $this->json($request['filter']);
            }
            $response = Async\await($this->privateGetExecutionTradeHistory ($request));
            //
            //     array(
            //         {
            //             "execID" => "string",
            //             "orderID" => "string",
            //             "clOrdID" => "string",
            //             "clOrdLinkID" => "string",
            //             "account" => 0,
            //             "symbol" => "string",
            //             "side" => "string",
            //             "lastQty" => 0,
            //             "lastPx" => 0,
            //             "underlyingLastPx" => 0,
            //             "lastMkt" => "string",
            //             "lastLiquidityInd" => "string",
            //             "simpleOrderQty" => 0,
            //             "orderQty" => 0,
            //             "price" => 0,
            //             "displayQty" => 0,
            //             "stopPx" => 0,
            //             "pegOffsetValue" => 0,
            //             "pegPriceType" => "string",
            //             "currency" => "string",
            //             "settlCurrency" => "string",
            //             "execType" => "string",
            //             "ordType" => "string",
            //             "timeInForce" => "string",
            //             "execInst" => "string",
            //             "contingencyType" => "string",
            //             "exDestination" => "string",
            //             "ordStatus" => "string",
            //             "triggered" => "string",
            //             "workingIndicator" => true,
            //             "ordRejReason" => "string",
            //             "simpleLeavesQty" => 0,
            //             "leavesQty" => 0,
            //             "simpleCumQty" => 0,
            //             "cumQty" => 0,
            //             "avgPx" => 0,
            //             "commission" => 0,
            //             "tradePublishIndicator" => "string",
            //             "multiLegReportingType" => "string",
            //             "text" => "string",
            //             "trdMatchID" => "string",
            //             "execCost" => 0,
            //             "execComm" => 0,
            //             "homeNotional" => 0,
            //             "foreignNotional" => 0,
            //             "transactTime" => "2019-03-05T12:47:02.762Z",
            //             "timestamp" => "2019-03-05T12:47:02.762Z"
            //         }
            //     )
            //
            return $this->parse_trades($response, $market, $since, $limit);
        }) ();
    }

    public function parse_ledger_entry_type($type) {
        $types = array(
            'Withdrawal' => 'transaction',
            'RealisedPNL' => 'margin',
            'UnrealisedPNL' => 'margin',
            'Deposit' => 'transaction',
            'Transfer' => 'transfer',
            'AffiliatePayout' => 'referral',
            'SpotTrade' => 'trade',
        );
        return $this->safe_string($types, $type, $type);
    }

    public function parse_ledger_entry(array $item, ?array $currency = null): array {
        //
        //     {
        //         "transactID" => "69573da3-7744-5467-3207-89fd6efe7a47",
        //         "account" =>  24321,
        //         "currency" => "XBt",
        //         "transactType" => "Withdrawal", // "AffiliatePayout", "Transfer", "Deposit", "RealisedPNL", ...
        //         "amount" =>  -1000000,
        //         "fee" =>  300000,
        //         "transactStatus" => "Completed", // "Canceled", ...
        //         "address" => "1Ex4fkF4NhQaQdRWNoYpqiPbDBbq18Kdd9",
        //         "tx" => "3BMEX91ZhhKoWtsH9QRb5dNXnmnGpiEetA",
        //         "text" => "",
        //         "transactTime" => "2017-03-21T20:05:14.388Z",
        //         "walletBalance" =>  0, // balance $after
        //         "marginBalance" =>  null,
        //         "timestamp" => "2017-03-22T13:09:23.514Z"
        //     }
        //
        // ButMEX returns the unrealized pnl from the wallet history endpoint.
        // The unrealized pnl transaction has an empty $timestamp->
        // It is not related to historical pnl it has $status set to "Pending".
        // Therefore it's not a part of the history at all.
        // https://github.com/ccxt/ccxt/issues/6047
        //
        //     {
        //         "transactID":"00000000-0000-0000-0000-000000000000",
        //         "account":121210,
        //         "currency":"XBt",
        //         "transactType":"UnrealisedPNL",
        //         "amount":-5508,
        //         "fee":0,
        //         "transactStatus":"Pending",
        //         "address":"XBTUSD",
        //         "tx":"",
        //         "text":"",
        //         "transactTime":null,  # ←---------------------------- null
        //         "walletBalance":139198767,
        //         "marginBalance":139193259,
        //         "timestamp":null  # ←---------------------------- null
        //     }
        //
        $id = $this->safe_string($item, 'transactID');
        $account = $this->safe_string($item, 'account');
        $referenceId = $this->safe_string($item, 'tx');
        $referenceAccount = null;
        $type = $this->parse_ledger_entry_type($this->safe_string($item, 'transactType'));
        $currencyId = $this->safe_string($item, 'currency');
        $code = $this->safe_currency_code($currencyId, $currency);
        $currency = $this->safe_currency($currencyId, $currency);
        $amountString = $this->safe_string($item, 'amount');
        $amount = $this->convert_to_real_amount($code, $amountString);
        $timestamp = $this->parse8601($this->safe_string($item, 'transactTime'));
        if ($timestamp === null) {
            // https://github.com/ccxt/ccxt/issues/6047
            // set the $timestamp to zero, 1970 Jan 1 00:00:00
            // for unrealized pnl and other transactions without a $timestamp
            $timestamp = 0; // see comments above
        }
        $fee = null;
        $feeCost = $this->safe_string($item, 'fee');
        if ($feeCost !== null) {
            $feeCost = $this->convert_to_real_amount($code, $feeCost);
            $fee = array(
                'cost' => $this->parse_number($feeCost),
                'currency' => $code,
            );
        }
        $after = $this->safe_string($item, 'walletBalance');
        if ($after !== null) {
            $after = $this->convert_to_real_amount($code, $after);
        }
        $before = $this->parse_number(Precise::string_sub($this->number_to_string($after), $this->number_to_string($amount)));
        $direction = null;
        if (Precise::string_lt($amountString, '0')) {
            $direction = 'out';
            $amount = $this->convert_to_real_amount($code, Precise::string_abs($amountString));
        } else {
            $direction = 'in';
        }
        $status = $this->parse_transaction_status($this->safe_string($item, 'transactStatus'));
        return $this->safe_ledger_entry(array(
            'info' => $item,
            'id' => $id,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'direction' => $direction,
            'account' => $account,
            'referenceId' => $referenceId,
            'referenceAccount' => $referenceAccount,
            'type' => $type,
            'currency' => $code,
            'amount' => $this->parse_number($amount),
            'before' => $before,
            'after' => $this->parse_number($after),
            'status' => $status,
            'fee' => $fee,
        ), $currency);
    }

    public function fetch_ledger(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch the history of changes, actions done by the user or operations that altered the balance of the user
             *
             * @see https://www.bitmex.com/api/explorer/#!/User/User_getWalletHistory
             *
             * @param {string} [$code] unified $currency $code, default is null
             * @param {int} [$since] timestamp in ms of the earliest ledger entry, default is null
             * @param {int} [$limit] max number of ledger entries to return, default is null
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=ledger ledger structure~
             */
            Async\await($this->load_markets());
            $request = array(
                // 'start' => 123,
            );
            //
            //     if ($since !== null) {
            //         // date-based pagination not supported
            //     }
            //
            if ($limit !== null) {
                $request['count'] = $limit;
            }
            $currency = null;
            if ($code !== null) {
                $currency = $this->currency($code);
                $request['currency'] = $currency['id'];
            }
            $response = Async\await($this->privateGetUserWalletHistory ($this->extend($request, $params)));
            //
            //     array(
            //         {
            //             "transactID" => "69573da3-7744-5467-3207-89fd6efe7a47",
            //             "account" =>  24321,
            //             "currency" => "XBt",
            //             "transactType" => "Withdrawal", // "AffiliatePayout", "Transfer", "Deposit", "RealisedPNL", ...
            //             "amount" =>  -1000000,
            //             "fee" =>  300000,
            //             "transactStatus" => "Completed", // "Canceled", ...
            //             "address" => "1Ex4fkF4NhQaQdRWNoYpqiPbDBbq18Kdd9",
            //             "tx" => "3BMEX91ZhhKoWtsH9QRb5dNXnmnGpiEetA",
            //             "text" => "",
            //             "transactTime" => "2017-03-21T20:05:14.388Z",
            //             "walletBalance" =>  0, // balance after
            //             "marginBalance" =>  null,
            //             "timestamp" => "2017-03-22T13:09:23.514Z"
            //         }
            //     )
            //
            return $this->parse_ledger($response, $currency, $since, $limit);
        }) ();
    }

    public function fetch_deposits_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch history of deposits and withdrawals
             *
             * @see https://www.bitmex.com/api/explorer/#!/User/User_getWalletHistory
             *
             * @param {string} [$code] unified $currency $code for the $currency of the deposit/withdrawals, default is null
             * @param {int} [$since] timestamp in ms of the earliest deposit/withdrawal, default is null
             * @param {int} [$limit] max number of deposit/withdrawals to return, default is null
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structure~
             */
            Async\await($this->load_markets());
            $request = array(
                'currency' => 'all',
                // 'start' => 123,
            );
            //
            //     if ($since !== null) {
            //         // date-based pagination not supported
            //     }
            //
            $currency = null;
            if ($code !== null) {
                $currency = $this->currency($code);
                $request['currency'] = $currency['id'];
            }
            if ($limit !== null) {
                $request['count'] = $limit;
            }
            $response = Async\await($this->privateGetUserWalletHistory ($this->extend($request, $params)));
            $transactions = $this->filter_by_array($response, 'transactType', array( 'Withdrawal', 'Deposit' ), false);
            return $this->parse_transactions($transactions, $currency, $since, $limit);
        }) ();
    }

    public function parse_transaction_status(?string $status) {
        $statuses = array(
            'Confirmed' => 'pending',
            'Canceled' => 'canceled',
            'Completed' => 'ok',
            'Pending' => 'pending',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_transaction(array $transaction, ?array $currency = null): array {
        //
        //    {
        //        "transactID" => "ffe699c2-95ee-4c13-91f9-0faf41daec25",
        //        "account" => 123456,
        //        "currency" => "XBt",
        //        "network":'', // "tron" for USDt, etc...
        //        "transactType" => "Withdrawal",
        //        "amount" => -100100000,
        //        "fee" => 100000,
        //        "transactStatus" => "Completed",
        //        "address" => "385cR5DM96n1HvBDMzLHPYcw89fZAXULJP",
        //        "tx" => "3BMEXabcdefghijklmnopqrstuvwxyz123",
        //        "text" => '',
        //        "transactTime" => "2019-01-02T01:00:00.000Z",
        //        "walletBalance" => 99900000, // this field might be inexistent
        //        "marginBalance" => None, // this field might be inexistent
        //        "timestamp" => "2019-01-02T13:00:00.000Z"
        //    }
        //
        $currencyId = $this->safe_string($transaction, 'currency');
        $currency = $this->safe_currency($currencyId, $currency);
        // For deposits, $transactTime == $timestamp
        // For withdrawals, $transactTime is submission, $timestamp is processed
        $transactTime = $this->parse8601($this->safe_string($transaction, 'transactTime'));
        $timestamp = $this->parse8601($this->safe_string($transaction, 'timestamp'));
        $type = $this->safe_string_lower($transaction, 'transactType');
        // Deposits have no from $address or to $address, withdrawals have both
        $address = null;
        $addressFrom = null;
        $addressTo = null;
        if ($type === 'withdrawal') {
            $address = $this->safe_string($transaction, 'address');
            $addressFrom = $this->safe_string($transaction, 'tx');
            $addressTo = $address;
        } elseif ($type === 'deposit') {
            $addressTo = $this->safe_string($transaction, 'address');
            $addressFrom = $this->safe_string($transaction, 'tx');
        }
        $amountString = $this->safe_string($transaction, 'amount');
        $amountStringAbs = Precise::string_abs($amountString);
        $amount = $this->convert_to_real_amount($currency['code'], $amountStringAbs);
        $feeCostString = $this->safe_string($transaction, 'fee');
        $feeCost = $this->convert_to_real_amount($currency['code'], $feeCostString);
        $status = $this->safe_string($transaction, 'transactStatus');
        if ($status !== null) {
            $status = $this->parse_transaction_status($status);
        }
        return array(
            'info' => $transaction,
            'id' => $this->safe_string($transaction, 'transactID'),
            'txid' => $this->safe_string($transaction, 'tx'),
            'type' => $type,
            'currency' => $currency['code'],
            'network' => $this->network_id_to_code($this->safe_string($transaction, 'network'), $currency['code']),
            'amount' => $this->parse_number($amount),
            'status' => $status,
            'timestamp' => $transactTime,
            'datetime' => $this->iso8601($transactTime),
            'address' => $address,
            'addressFrom' => $addressFrom,
            'addressTo' => $addressTo,
            'tag' => null,
            'tagFrom' => null,
            'tagTo' => null,
            'updated' => $timestamp,
            'internal' => null,
            'comment' => null,
            'fee' => array(
                'currency' => $currency['code'],
                'cost' => $this->parse_number($feeCost),
                'rate' => null,
            ),
        );
    }

    public function fetch_ticker(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetches a price $ticker, a statistical calculation with the information calculated over the past 24 hours for a specific $market
             *
             * @see https://www.bitmex.com/api/explorer/#!/Instrument/Instrument_get
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the $ticker for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=$ticker-structure $ticker structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
            );
            $response = Async\await($this->publicGetInstrument ($this->extend($request, $params)));
            $ticker = $this->safe_value($response, 0);
            if ($ticker === null) {
                throw new BadSymbol($this->id . ' fetchTicker() $symbol ' . $symbol . ' not found');
            }
            return $this->parse_ticker($ticker, $market);
        }) ();
    }

    public function fetch_tickers(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
             *
             * @see https://www.bitmex.com/api/explorer/#!/Instrument/Instrument_getActiveAndIndices
             *
             * @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~
             */
            Async\await($this->load_markets());
            $symbols = $this->market_symbols($symbols);
            $response = Async\await($this->publicGetInstrumentActiveAndIndices ($params));
            // same $response "fetchMarkets"
            $result = array();
            for ($i = 0; $i < count($response); $i++) {
                $ticker = $this->parse_ticker($response[$i]);
                $symbol = $this->safe_string($ticker, 'symbol');
                if ($symbol !== null) {
                    $result[$symbol] = $ticker;
                }
            }
            return $this->filter_by_array_tickers($result, 'symbol', $symbols);
        }) ();
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        // see response sample under "fetchMarkets" because same endpoint is being used here
        $marketId = $this->safe_string($ticker, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $timestamp = $this->parse8601($this->safe_string($ticker, 'timestamp'));
        $open = $this->safe_string($ticker, 'prevPrice24h');
        $last = $this->safe_string($ticker, 'lastPrice');
        return $this->safe_ticker(array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_string($ticker, 'highPrice'),
            'low' => $this->safe_string($ticker, 'lowPrice'),
            'bid' => $this->safe_string($ticker, 'bidPrice'),
            'bidVolume' => null,
            'ask' => $this->safe_string($ticker, 'askPrice'),
            'askVolume' => null,
            'vwap' => $this->safe_string($ticker, 'vwap'),
            'open' => $open,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => null,
            'percentage' => null,
            'average' => null,
            'baseVolume' => $this->safe_string($ticker, 'homeNotional24h'),
            'quoteVolume' => $this->safe_string($ticker, 'foreignNotional24h'),
            'markPrice' => $this->safe_string($ticker, 'markPrice'),
            'info' => $ticker,
        ), $market);
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        //
        //     {
        //         "timestamp":"2015-09-25T13:38:00.000Z",
        //         "symbol":"XBTUSD",
        //         "open":237.45,
        //         "high":237.45,
        //         "low":237.45,
        //         "close":237.45,
        //         "trades":0,
        //         "volume":0,
        //         "vwap":null,
        //         "lastSize":null,
        //         "turnover":0,
        //         "homeNotional":0,
        //         "foreignNotional":0
        //     }
        //
        $marketId = $this->safe_string($ohlcv, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $volume = $this->convert_from_raw_quantity($market['symbol'], $this->safe_string($ohlcv, 'volume'));
        return array(
            $this->parse8601($this->safe_string($ohlcv, 'timestamp')),
            $this->safe_number($ohlcv, 'open'),
            $this->safe_number($ohlcv, 'high'),
            $this->safe_number($ohlcv, 'low'),
            $this->safe_number($ohlcv, 'close'),
            $volume,
        );
    }

    public function fetch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $timeframe, $since, $limit, $params) {
            /**
             * fetches historical candlestick data containing the open, high, low, and close price, and the volume of a $market
             *
             * @see https://www.bitmex.com/api/explorer/#!/Trade/Trade_getBucketed
             *
             * @param {string} $symbol unified $symbol of the $market to fetch OHLCV data for
             * @param {string} $timeframe the length of time each candle represents
             * @param {int} [$since] $timestamp in ms of the earliest candle to fetch
             * @param {int} [$limit] the maximum amount of candles to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {int[][]} A list of candles ordered, open, high, low, close, volume
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchOHLCV', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_deterministic('fetchOHLCV', $symbol, $since, $limit, $timeframe, $params));
            }
            // send JSON key/value pairs, such as array("key" => "value")
            // $filter by individual fields and do advanced queries on timestamps
            // $filter = array( 'key' => 'value' );
            // send a bare series (e.g. XBU) to nearest expiring contract in that series
            // you can also send a $timeframe, e.g. XBU:monthly
            // timeframes => daily, weekly, monthly, quarterly, and biquarterly
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
                'binSize' => $this->safe_string($this->timeframes, $timeframe, $timeframe),
                'partial' => true,     // true == include yet-incomplete current bins
                // 'filter' => $filter, // $filter by individual fields and do advanced queries
                // 'columns' => array(),    // will return all columns if omitted
                // 'start' => 0,       // starting point for results (wtf?)
                // 'reverse' => false, // true == newest first
                // 'endTime' => '',    // ending date $filter for results
            );
            if ($limit !== null) {
                $request['count'] = $limit; // default 100, max 500
            }
            $until = $this->safe_integer($params, 'until');
            if ($until !== null) {
                $params = $this->omit($params, array( 'until' ));
                $request['endTime'] = $this->iso8601($until);
            }
            $duration = $this->parse_timeframe($timeframe) * 1000;
            $fetchOHLCVOpenTimestamp = $this->safe_bool($this->options, 'fetchOHLCVOpenTimestamp', true);
            // if $since is not set, they will return candles starting from 2017-01-01
            if ($since !== null) {
                $timestamp = $since;
                if ($fetchOHLCVOpenTimestamp) {
                    $timestamp = $this->sum($timestamp, $duration);
                }
                $startTime = $this->iso8601($timestamp);
                $request['startTime'] = $startTime; // starting date $filter for results
            } else {
                $request['reverse'] = true;
            }
            $response = Async\await($this->publicGetTradeBucketed ($this->extend($request, $params)));
            //
            //     array(
            //         array("timestamp":"2015-09-25T13:38:00.000Z","symbol":"XBTUSD","open":237.45,"high":237.45,"low":237.45,"close":237.45,"trades":0,"volume":0,"vwap":null,"lastSize":null,"turnover":0,"homeNotional":0,"foreignNotional":0),
            //         array("timestamp":"2015-09-25T13:39:00.000Z","symbol":"XBTUSD","open":237.45,"high":237.45,"low":237.45,"close":237.45,"trades":0,"volume":0,"vwap":null,"lastSize":null,"turnover":0,"homeNotional":0,"foreignNotional":0),
            //         array("timestamp":"2015-09-25T13:40:00.000Z","symbol":"XBTUSD","open":237.45,"high":237.45,"low":237.45,"close":237.45,"trades":0,"volume":0,"vwap":null,"lastSize":null,"turnover":0,"homeNotional":0,"foreignNotional":0)
            //     )
            //
            $result = $this->parse_ohlcvs($response, $market, $timeframe, $since, $limit);
            if ($fetchOHLCVOpenTimestamp) {
                // bitmex returns the candle's close $timestamp - https://github.com/ccxt/ccxt/issues/4446
                // we can emulate the open $timestamp by shifting all the timestamps one place
                // so the previous close becomes the current open, and we drop the first candle
                for ($i = 0; $i < count($result); $i++) {
                    $result[$i][0] = $result[$i][0] - $duration;
                }
            }
            return $result;
        }) ();
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // fetchTrades (public)
        //
        //     {
        //         "timestamp" => "2018-08-28T00:00:02.735Z",
        //         "symbol" => "XBTUSD",
        //         "side" => "Buy",
        //         "size" => 2000,
        //         "price" => 6906.5,
        //         "tickDirection" => "PlusTick",
        //         "trdMatchID" => "b9a42432-0a46-6a2f-5ecc-c32e9ca4baf8",
        //         "grossValue" => 28958000,
        //         "homeNotional" => 0.28958,
        //         "foreignNotional" => 2000
        //     }
        //
        // fetchMyTrades (private)
        //
        //     {
        //         "execID" => "string",
        //         "orderID" => "string",
        //         "clOrdID" => "string",
        //         "clOrdLinkID" => "string",
        //         "account" => 0,
        //         "symbol" => "string",
        //         "side" => "string",
        //         "lastQty" => 0,
        //         "lastPx" => 0,
        //         "underlyingLastPx" => 0,
        //         "lastMkt" => "string",
        //         "lastLiquidityInd" => "string",
        //         "simpleOrderQty" => 0,
        //         "orderQty" => 0,
        //         "price" => 0,
        //         "displayQty" => 0,
        //         "stopPx" => 0,
        //         "pegOffsetValue" => 0,
        //         "pegPriceType" => "string",
        //         "currency" => "string",
        //         "settlCurrency" => "string",
        //         "execType" => "string",
        //         "ordType" => "string",
        //         "timeInForce" => "string",
        //         "execInst" => "string",
        //         "contingencyType" => "string",
        //         "exDestination" => "string",
        //         "ordStatus" => "string",
        //         "triggered" => "string",
        //         "workingIndicator" => true,
        //         "ordRejReason" => "string",
        //         "simpleLeavesQty" => 0,
        //         "leavesQty" => 0,
        //         "simpleCumQty" => 0,
        //         "cumQty" => 0,
        //         "avgPx" => 0,
        //         "commission" => 0,
        //         "tradePublishIndicator" => "string",
        //         "multiLegReportingType" => "string",
        //         "text" => "string",
        //         "trdMatchID" => "string",
        //         "execCost" => 0,
        //         "execComm" => 0,
        //         "homeNotional" => 0,
        //         "foreignNotional" => 0,
        //         "transactTime" => "2019-03-05T12:47:02.762Z",
        //         "timestamp" => "2019-03-05T12:47:02.762Z"
        //     }
        //
        $marketId = $this->safe_string($trade, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $timestamp = $this->parse8601($this->safe_string($trade, 'timestamp'));
        $priceString = $this->safe_string_2($trade, 'avgPx', 'price');
        $amountString = $this->convert_from_raw_quantity($symbol, $this->safe_string_2($trade, 'size', 'lastQty'));
        $execCost = $this->number_to_string($this->convert_from_raw_cost($symbol, $this->safe_string($trade, 'execCost')));
        $id = $this->safe_string($trade, 'trdMatchID');
        $order = $this->safe_string($trade, 'orderID');
        $side = $this->safe_string_lower($trade, 'side');
        // price * amount doesn't work for all symbols (e.g. XBT, ETH)
        $fee = null;
        $feeCostString = $this->number_to_string($this->convert_from_raw_cost($symbol, $this->safe_string($trade, 'execComm')));
        if ($feeCostString !== null) {
            $currencyId = $this->safe_string_2($trade, 'settlCurrency', 'currency');
            $fee = array(
                'cost' => $feeCostString,
                'currency' => $this->safe_currency_code($currencyId),
                'rate' => $this->safe_string($trade, 'commission'),
            );
        }
        // Trade or Funding
        $execType = $this->safe_string($trade, 'execType');
        $takerOrMaker = null;
        if ($feeCostString !== null && $execType === 'Trade') {
            $takerOrMaker = Precise::string_lt($feeCostString, '0') ? 'maker' : 'taker';
        }
        $type = $this->safe_string_lower($trade, 'ordType');
        return $this->safe_trade(array(
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'id' => $id,
            'order' => $order,
            'type' => $type,
            'takerOrMaker' => $takerOrMaker,
            'side' => $side,
            'price' => $priceString,
            'cost' => Precise::string_abs($execCost),
            'amount' => $amountString,
            'fee' => $fee,
        ), $market);
    }

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

    public function parse_time_in_force(?string $timeInForce) {
        $timeInForces = array(
            'Day' => 'Day',
            'GoodTillCancel' => 'GTC',
            'ImmediateOrCancel' => 'IOC',
            'FillOrKill' => 'FOK',
        );
        return $this->safe_string($timeInForces, $timeInForce, $timeInForce);
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        //     {
        //         "orderID":"56222c7a-9956-413a-82cf-99f4812c214b",
        //         "clOrdID":"",
        //         "clOrdLinkID":"",
        //         "account":1455728,
        //         "symbol":"XBTUSD",
        //         "side":"Sell",
        //         "simpleOrderQty":null,
        //         "orderQty":1,
        //         "price":40000,
        //         "displayQty":null,
        //         "stopPx":null,
        //         "pegOffsetValue":null,
        //         "pegPriceType":"",
        //         "currency":"USD",
        //         "settlCurrency":"XBt",
        //         "ordType":"Limit",
        //         "timeInForce":"GoodTillCancel",
        //         "execInst":"",
        //         "contingencyType":"",
        //         "exDestination":"XBME",
        //         "ordStatus":"New",
        //         "triggered":"",
        //         "workingIndicator":true,
        //         "ordRejReason":"",
        //         "simpleLeavesQty":null,
        //         "leavesQty":1,
        //         "simpleCumQty":null,
        //         "cumQty":0,
        //         "avgPx":null,
        //         "multiLegReportingType":"SingleSecurity",
        //         "text":"Submitted via API.",
        //         "transactTime":"2021-01-02T21:38:49.246Z",
        //         "timestamp":"2021-01-02T21:38:49.246Z"
        //     }
        //
        $marketId = $this->safe_string($order, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $qty = $this->safe_string($order, 'orderQty');
        $cost = null;
        $amount = null;
        $isInverse = false;
        if ($marketId === null) {
            $defaultSubType = $this->safe_string($this->options, 'defaultSubType', 'linear');
            $isInverse = ($defaultSubType === 'inverse');
        } else {
            $isInverse = $this->safe_bool($market, 'inverse', false);
        }
        if ($isInverse) {
            $cost = $this->convert_from_raw_quantity($symbol, $qty);
        } else {
            $amount = $this->convert_from_raw_quantity($symbol, $qty);
        }
        $average = $this->safe_string($order, 'avgPx');
        $filled = null;
        $cumQty = $this->number_to_string($this->convert_from_raw_quantity($symbol, $this->safe_string($order, 'cumQty')));
        if ($isInverse) {
            $filled = Precise::string_div($cumQty, $average);
        } else {
            $filled = $cumQty;
        }
        $execInst = $this->safe_string($order, 'execInst');
        $postOnly = null;
        if ($execInst !== null) {
            $postOnly = ($execInst === 'ParticipateDoNotInitiate');
        }
        $timestamp = $this->parse8601($this->safe_string($order, 'timestamp'));
        $triggerPrice = $this->safe_number($order, 'stopPx');
        $remaining = $this->safe_string($order, 'leavesQty');
        return $this->safe_order(array(
            'info' => $order,
            'id' => $this->safe_string($order, 'orderID'),
            'clientOrderId' => $this->safe_string($order, 'clOrdID'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => $this->parse8601($this->safe_string($order, 'transactTime')),
            'symbol' => $symbol,
            'type' => $this->safe_string_lower($order, 'ordType'),
            'timeInForce' => $this->parse_time_in_force($this->safe_string($order, 'timeInForce')),
            'postOnly' => $postOnly,
            'side' => $this->safe_string_lower($order, 'side'),
            'price' => $this->safe_string($order, 'price'),
            'triggerPrice' => $triggerPrice,
            'amount' => $amount,
            'cost' => $cost,
            'average' => $average,
            'filled' => $filled,
            'remaining' => $this->convert_from_raw_quantity($symbol, $remaining),
            'status' => $this->parse_order_status($this->safe_string($order, 'ordStatus')),
            'fee' => null,
            'trades' => null,
        ), $market);
    }

    public function fetch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * get the list of most recent trades for a particular $symbol
             *
             * @see https://www.bitmex.com/api/explorer/#!/Trade/Trade_get
             *
             * @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 {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-trades trade structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchTrades', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_dynamic('fetchTrades', $symbol, $since, $limit, $params));
            }
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
            );
            if ($since !== null) {
                $request['startTime'] = $this->iso8601($since);
            } else {
                // by default reverse=false, i.e. trades are fetched $since the time of $market inception (year 2015 for XBTUSD)
                $request['reverse'] = true;
            }
            if ($limit !== null) {
                $request['count'] = min ($limit, 1000); // api maximum 1000
            }
            $until = $this->safe_integer_2($params, 'until', 'endTime');
            if ($until !== null) {
                $params = $this->omit($params, array( 'until' ));
                $request['endTime'] = $this->iso8601($until);
            }
            $response = Async\await($this->publicGetTrade ($this->extend($request, $params)));
            //
            //     array(
            //         array(
            //             "timestamp" => "2018-08-28T00:00:02.735Z",
            //             "symbol" => "XBTUSD",
            //             "side" => "Buy",
            //             "size" => 2000,
            //             "price" => 6906.5,
            //             "tickDirection" => "PlusTick",
            //             "trdMatchID" => "b9a42432-0a46-6a2f-5ecc-c32e9ca4baf8",
            //             "grossValue" => 28958000,
            //             "homeNotional" => 0.28958,
            //             "foreignNotional" => 2000
            //         ),
            //         array(
            //             "timestamp" => "2018-08-28T00:00:03.778Z",
            //             "symbol" => "XBTUSD",
            //             "side" => "Sell",
            //             "size" => 1000,
            //             "price" => 6906,
            //             "tickDirection" => "MinusTick",
            //             "trdMatchID" => "0d4f1682-5270-a800-569b-4a0eb92db97c",
            //             "grossValue" => 14480000,
            //             "homeNotional" => 0.1448,
            //             "foreignNotional" => 1000
            //         ),
            //     )
            //
            return $this->parse_trades($response, $market, $since, $limit);
        }) ();
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        return Async\async(function () use ($symbol, $type, $side, $amount, $price, $params) {
            /**
             * create a trade order
             *
             * @see https://www.bitmex.com/api/explorer/#!/Order/Order_new
             *
             * @param {string} $symbol unified $symbol of the $market to create an order in
             * @param {string} $type 'market' or 'limit'
             * @param {string} $side 'buy' or 'sell'
             * @param {float} $amount how much of currency you want to trade in units of base currency
             * @param {float} [$price] the $price at which the order is to be fulfilled, in units of the quote currency, ignored in $market orders
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {array} [$params->triggerPrice] the $price at which a trigger order is triggered at
             * @param {array} [$params->triggerDirection] the direction whenever the trigger happens with relation to $price - 'ascending' or 'descending'
             * @param {float} [$params->trailingAmount] the quote $amount to trail away from the current $market $price
             * @return {array} an {@link https://github.com/ccxt/ccxt/wiki/Manual#order-structure order structure}
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $orderType = $this->capitalize($type);
            $reduceOnly = $this->safe_value($params, 'reduceOnly');
            if ($reduceOnly !== null) {
                if ((!$market['swap']) && (!$market['future'])) {
                    throw new InvalidOrder($this->id . ' createOrder() does not support $reduceOnly for ' . $market['type'] . ' orders, $reduceOnly orders are supported for swap and future markets only');
                }
            }
            $brokerId = $this->safe_string($this->options, 'brokerId', 'CCXT');
            $qty = $this->parse_to_int($this->amount_to_precision($symbol, $amount));
            $request = array(
                'symbol' => $market['id'],
                'side' => $this->capitalize($side),
                'orderQty' => $qty, // lot size multiplied by the number of contracts
                'ordType' => $orderType,
                'text' => $brokerId,
            );
            // support for unified trigger format
            $triggerPrice = $this->safe_number_n($params, array( 'triggerPrice', 'stopPx', 'stopPrice' ));
            $trailingAmount = $this->safe_string_2($params, 'trailingAmount', 'pegOffsetValue');
            $isTriggerOrder = $triggerPrice !== null;
            $isTrailingAmountOrder = $trailingAmount !== null;
            if ($isTriggerOrder || $isTrailingAmountOrder) {
                $triggerDirection = $this->safe_string($params, 'triggerDirection');
                $triggerAbove = (($triggerDirection === 'ascending') || ($triggerDirection === 'above'));
                if (($type === 'limit') || ($type === 'market')) {
                    $this->check_required_argument('createOrder', $triggerDirection, 'triggerDirection', array( 'above', 'below' ));
                }
                if ($type === 'limit') {
                    if ($side === 'buy') {
                        $orderType = $triggerAbove ? 'StopLimit' : 'LimitIfTouched';
                    } else {
                        $orderType = $triggerAbove ? 'LimitIfTouched' : 'StopLimit';
                    }
                } elseif ($type === 'market') {
                    if ($side === 'buy') {
                        $orderType = $triggerAbove ? 'Stop' : 'MarketIfTouched';
                    } else {
                        $orderType = $triggerAbove ? 'MarketIfTouched' : 'Stop';
                    }
                }
                if ($isTrailingAmountOrder) {
                    $isStopSellOrder = ($side === 'sell') && (($orderType === 'Stop') || ($orderType === 'StopLimit'));
                    $isBuyIfTouchedOrder = ($side === 'buy') && (($orderType === 'MarketIfTouched') || ($orderType === 'LimitIfTouched'));
                    if ($isStopSellOrder || $isBuyIfTouchedOrder) {
                        $trailingAmount = '-' . $trailingAmount;
                    }
                    $request['pegOffsetValue'] = $this->parse_to_numeric($trailingAmount);
                    $request['pegPriceType'] = 'TrailingStopPeg';
                } else {
                    if ($triggerPrice === null) {
                        // if exchange specific trigger types were provided
                        throw new ArgumentsRequired($this->id . ' createOrder() requires a $triggerPrice parameter for the ' . $orderType . ' order type');
                    }
                    $request['stopPx'] = $this->parse_to_numeric($this->price_to_precision($symbol, $triggerPrice));
                }
                $request['ordType'] = $orderType;
                $params = $this->omit($params, array( 'triggerPrice', 'stopPrice', 'stopPx', 'triggerDirection', 'trailingAmount' ));
            }
            if (($orderType === 'Limit') || ($orderType === 'StopLimit') || ($orderType === 'LimitIfTouched')) {
                $request['price'] = $this->parse_to_numeric($this->price_to_precision($symbol, $price));
            }
            $clientOrderId = $this->safe_string_2($params, 'clOrdID', 'clientOrderId');
            if ($clientOrderId !== null) {
                $request['clOrdID'] = $clientOrderId;
                $params = $this->omit($params, array( 'clOrdID', 'clientOrderId' ));
            }
            $response = Async\await($this->privatePostOrder ($this->extend($request, $params)));
            return $this->parse_order($response, $market);
        }) ();
    }

    public function edit_order(string $id, string $symbol, string $type, string $side, ?float $amount = null, ?float $price = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $type, $side, $amount, $price, $params) {
            Async\await($this->load_markets());
            $request = array();
            $trailingAmount = $this->safe_string_2($params, 'trailingAmount', 'pegOffsetValue');
            $isTrailingAmountOrder = $trailingAmount !== null;
            if ($isTrailingAmountOrder) {
                $triggerDirection = $this->safe_string($params, 'triggerDirection');
                $triggerAbove = (($triggerDirection === 'ascending') || ($triggerDirection === 'above'));
                if (($type === 'limit') || ($type === 'market')) {
                    $this->check_required_argument('createOrder', $triggerDirection, 'triggerDirection', array( 'above', 'below' ));
                }
                $orderType = null;
                if ($type === 'limit') {
                    if ($side === 'buy') {
                        $orderType = $triggerAbove ? 'StopLimit' : 'LimitIfTouched';
                    } else {
                        $orderType = $triggerAbove ? 'LimitIfTouched' : 'StopLimit';
                    }
                } elseif ($type === 'market') {
                    if ($side === 'buy') {
                        $orderType = $triggerAbove ? 'Stop' : 'MarketIfTouched';
                    } else {
                        $orderType = $triggerAbove ? 'MarketIfTouched' : 'Stop';
                    }
                }
                $isStopSellOrder = ($side === 'sell') && (($orderType === 'Stop') || ($orderType === 'StopLimit'));
                $isBuyIfTouchedOrder = ($side === 'buy') && (($orderType === 'MarketIfTouched') || ($orderType === 'LimitIfTouched'));
                if ($isStopSellOrder || $isBuyIfTouchedOrder) {
                    $trailingAmount = '-' . $trailingAmount;
                }
                $request['pegOffsetValue'] = $this->parse_to_numeric($trailingAmount);
                $params = $this->omit($params, array( 'triggerDirection', 'trailingAmount' ));
            }
            $origClOrdID = $this->safe_string_2($params, 'origClOrdID', 'clientOrderId');
            if ($origClOrdID !== null) {
                $request['origClOrdID'] = $origClOrdID;
                $clientOrderId = $this->safe_string($params, 'clOrdID', 'clientOrderId');
                if ($clientOrderId !== null) {
                    $request['clOrdID'] = $clientOrderId;
                }
                $params = $this->omit($params, array( 'origClOrdID', 'clOrdID', 'clientOrderId' ));
            } else {
                $request['orderID'] = $id;
            }
            if ($amount !== null) {
                $qty = $this->parse_to_int($this->amount_to_precision($symbol, $amount));
                $request['orderQty'] = $qty;
            }
            if ($price !== null) {
                $request['price'] = $price;
            }
            $brokerId = $this->safe_string($this->options, 'brokerId', 'CCXT');
            $request['text'] = $brokerId;
            $response = Async\await($this->privatePutOrder ($this->extend($request, $params)));
            return $this->parse_order($response);
        }) ();
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $params) {
            /**
             * cancels an open $order
             *
             * @see https://www.bitmex.com/api/explorer/#!/Order/Order_cancel
             *
             * @param {string} $id $order $id
             * @param {string} $symbol not used by bitmex cancelOrder ()
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} An ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structure~
             */
            Async\await($this->load_markets());
            // https://github.com/ccxt/ccxt/issues/6507
            $clientOrderId = $this->safe_value_2($params, 'clOrdID', 'clientOrderId');
            $request = array();
            if ($clientOrderId === null) {
                $request['orderID'] = $id;
            } else {
                $request['clOrdID'] = $clientOrderId;
                $params = $this->omit($params, array( 'clOrdID', 'clientOrderId' ));
            }
            $response = Async\await($this->privateDeleteOrder ($this->extend($request, $params)));
            $order = $this->safe_value($response, 0, array());
            $error = $this->safe_string($order, 'error');
            if ($error !== null) {
                if (mb_strpos($error, 'Unable to cancel $order due to existing state') !== false) {
                    throw new OrderNotFound($this->id . ' cancelOrder() failed => ' . $error);
                }
            }
            return $this->parse_order($order);
        }) ();
    }

    public function cancel_orders($ids, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($ids, $symbol, $params) {
            /**
             * cancel multiple orders
             *
             * @see https://www.bitmex.com/api/explorer/#!/Order/Order_cancel
             *
             * @param {string[]} $ids order $ids
             * @param {string} $symbol not used by bitmex cancelOrders ()
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            // return Async\await($this->cancel_order($ids, $symbol, $params));
            Async\await($this->load_markets());
            // https://github.com/ccxt/ccxt/issues/6507
            $clientOrderId = $this->safe_value_2($params, 'clOrdID', 'clientOrderId');
            $request = array();
            if ($clientOrderId === null) {
                $request['orderID'] = $ids;
            } else {
                $request['clOrdID'] = $clientOrderId;
                $params = $this->omit($params, array( 'clOrdID', 'clientOrderId' ));
            }
            $response = Async\await($this->privateDeleteOrder ($this->extend($request, $params)));
            return $this->parse_orders($response);
        }) ();
    }

    public function cancel_all_orders(?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             * cancel all open orders
             *
             * @see https://www.bitmex.com/api/explorer/#!/Order/Order_cancelAll
             *
             * @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~
             */
            Async\await($this->load_markets());
            $request = array();
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['symbol'] = $market['id'];
            }
            $response = Async\await($this->privateDeleteOrderAll ($this->extend($request, $params)));
            //
            //     array(
            //         {
            //             "orderID" => "string",
            //             "clOrdID" => "string",
            //             "clOrdLinkID" => "string",
            //             "account" => 0,
            //             "symbol" => "string",
            //             "side" => "string",
            //             "simpleOrderQty" => 0,
            //             "orderQty" => 0,
            //             "price" => 0,
            //             "displayQty" => 0,
            //             "stopPx" => 0,
            //             "pegOffsetValue" => 0,
            //             "pegPriceType" => "string",
            //             "currency" => "string",
            //             "settlCurrency" => "string",
            //             "ordType" => "string",
            //             "timeInForce" => "string",
            //             "execInst" => "string",
            //             "contingencyType" => "string",
            //             "exDestination" => "string",
            //             "ordStatus" => "string",
            //             "triggered" => "string",
            //             "workingIndicator" => true,
            //             "ordRejReason" => "string",
            //             "simpleLeavesQty" => 0,
            //             "leavesQty" => 0,
            //             "simpleCumQty" => 0,
            //             "cumQty" => 0,
            //             "avgPx" => 0,
            //             "multiLegReportingType" => "string",
            //             "text" => "string",
            //             "transactTime" => "2020-06-01T09:36:35.290Z",
            //             "timestamp" => "2020-06-01T09:36:35.290Z"
            //         }
            //     )
            //
            return $this->parse_orders($response, $market);
        }) ();
    }

    public function cancel_all_orders_after(?int $timeout, $params = array ()) {
        return Async\async(function () use ($timeout, $params) {
            /**
             * dead man's switch, cancel all orders after the given $timeout
             *
             * @see https://www.bitmex.com/api/explorer/#!/Order/Order_cancelAllAfter
             *
             * @param {number} $timeout time in milliseconds, 0 represents cancel the timer
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} the api result
             */
            Async\await($this->load_markets());
            $request = array(
                'timeout' => ($timeout > 0) ? $this->parse_to_int($timeout / 1000) : 0,
            );
            $response = Async\await($this->privatePostOrderCancelAllAfter ($this->extend($request, $params)));
            //
            //     {
            //         now => '2024-04-09T09:01:56.560Z',
            //         cancelTime => '2024-04-09T09:01:56.660Z'
            //     }
            //
            return $response;
        }) ();
    }

    public function fetch_leverages(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetch the set leverage for all contract markets
             *
             * @see https://www.bitmex.com/api/explorer/#!/Position/Position_get
             *
             * @param {string[]} [$symbols] a list of unified market $symbols
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a list of ~@link https://docs.ccxt.com/#/?id=leverage-structure leverage structures~
             */
            Async\await($this->load_markets());
            $leverages = Async\await($this->fetch_positions($symbols, $params));
            return $this->parse_leverages($leverages, $symbols, 'symbol');
        }) ();
    }

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

    public function fetch_positions(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetch all open positions
             *
             * @see https://www.bitmex.com/api/explorer/#!/Position/Position_get
             *
             * @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~
             */
            Async\await($this->load_markets());
            $response = Async\await($this->privateGetPosition ($params));
            //
            //     array(
            //         {
            //             "account" => 0,
            //             "symbol" => "string",
            //             "currency" => "string",
            //             "underlying" => "string",
            //             "quoteCurrency" => "string",
            //             "commission" => 0,
            //             "initMarginReq" => 0,
            //             "maintMarginReq" => 0,
            //             "riskLimit" => 0,
            //             "leverage" => 0,
            //             "crossMargin" => true,
            //             "deleveragePercentile" => 0,
            //             "rebalancedPnl" => 0,
            //             "prevRealisedPnl" => 0,
            //             "prevUnrealisedPnl" => 0,
            //             "prevClosePrice" => 0,
            //             "openingTimestamp" => "2020-11-09T06:53:59.892Z",
            //             "openingQty" => 0,
            //             "openingCost" => 0,
            //             "openingComm" => 0,
            //             "openOrderBuyQty" => 0,
            //             "openOrderBuyCost" => 0,
            //             "openOrderBuyPremium" => 0,
            //             "openOrderSellQty" => 0,
            //             "openOrderSellCost" => 0,
            //             "openOrderSellPremium" => 0,
            //             "execBuyQty" => 0,
            //             "execBuyCost" => 0,
            //             "execSellQty" => 0,
            //             "execSellCost" => 0,
            //             "execQty" => 0,
            //             "execCost" => 0,
            //             "execComm" => 0,
            //             "currentTimestamp" => "2020-11-09T06:53:59.893Z",
            //             "currentQty" => 0,
            //             "currentCost" => 0,
            //             "currentComm" => 0,
            //             "realisedCost" => 0,
            //             "unrealisedCost" => 0,
            //             "grossOpenCost" => 0,
            //             "grossOpenPremium" => 0,
            //             "grossExecCost" => 0,
            //             "isOpen" => true,
            //             "markPrice" => 0,
            //             "markValue" => 0,
            //             "riskValue" => 0,
            //             "homeNotional" => 0,
            //             "foreignNotional" => 0,
            //             "posState" => "string",
            //             "posCost" => 0,
            //             "posCost2" => 0,
            //             "posCross" => 0,
            //             "posInit" => 0,
            //             "posComm" => 0,
            //             "posLoss" => 0,
            //             "posMargin" => 0,
            //             "posMaint" => 0,
            //             "posAllowance" => 0,
            //             "taxableMargin" => 0,
            //             "initMargin" => 0,
            //             "maintMargin" => 0,
            //             "sessionMargin" => 0,
            //             "targetExcessMargin" => 0,
            //             "varMargin" => 0,
            //             "realisedGrossPnl" => 0,
            //             "realisedTax" => 0,
            //             "realisedPnl" => 0,
            //             "unrealisedGrossPnl" => 0,
            //             "longBankrupt" => 0,
            //             "shortBankrupt" => 0,
            //             "taxBase" => 0,
            //             "indicativeTaxRate" => 0,
            //             "indicativeTax" => 0,
            //             "unrealisedTax" => 0,
            //             "unrealisedPnl" => 0,
            //             "unrealisedPnlPcnt" => 0,
            //             "unrealisedRoePcnt" => 0,
            //             "simpleQty" => 0,
            //             "simpleCost" => 0,
            //             "simpleValue" => 0,
            //             "simplePnl" => 0,
            //             "simplePnlPcnt" => 0,
            //             "avgCostPrice" => 0,
            //             "avgEntryPrice" => 0,
            //             "breakEvenPrice" => 0,
            //             "marginCallPrice" => 0,
            //             "liquidationPrice" => 0,
            //             "bankruptPrice" => 0,
            //             "timestamp" => "2020-11-09T06:53:59.894Z",
            //             "lastPrice" => 0,
            //             "lastValue" => 0
            //         }
            //     )
            //
            $results = $this->parse_positions($response, $symbols);
            return $this->filter_by_array_positions($results, 'symbol', $symbols, false);
        }) ();
    }

    public function parse_position(array $position, ?array $market = null) {
        //
        //     {
        //         "account" => 9371654,
        //         "symbol" => "ETHUSDT",
        //         "currency" => "USDt",
        //         "underlying" => "ETH",
        //         "quoteCurrency" => "USDT",
        //         "commission" => 0.00075,
        //         "initMarginReq" => 0.3333333333333333,
        //         "maintMarginReq" => 0.01,
        //         "riskLimit" => 1000000000000,
        //         "leverage" => 3,
        //         "crossMargin" => false,
        //         "deleveragePercentile" => 1,
        //         "rebalancedPnl" => 0,
        //         "prevRealisedPnl" => 0,
        //         "prevUnrealisedPnl" => 0,
        //         "prevClosePrice" => 2053.738,
        //         "openingTimestamp" => "2022-05-21T04:00:00.000Z",
        //         "openingQty" => 0,
        //         "openingCost" => 0,
        //         "openingComm" => 0,
        //         "openOrderBuyQty" => 0,
        //         "openOrderBuyCost" => 0,
        //         "openOrderBuyPremium" => 0,
        //         "openOrderSellQty" => 0,
        //         "openOrderSellCost" => 0,
        //         "openOrderSellPremium" => 0,
        //         "execBuyQty" => 2000,
        //         "execBuyCost" => 39260000,
        //         "execSellQty" => 0,
        //         "execSellCost" => 0,
        //         "execQty" => 2000,
        //         "execCost" => 39260000,
        //         "execComm" => 26500,
        //         "currentTimestamp" => "2022-05-21T04:35:16.397Z",
        //         "currentQty" => 2000,
        //         "currentCost" => 39260000,
        //         "currentComm" => 26500,
        //         "realisedCost" => 0,
        //         "unrealisedCost" => 39260000,
        //         "grossOpenCost" => 0,
        //         "grossOpenPremium" => 0,
        //         "grossExecCost" => 39260000,
        //         "isOpen" => true,
        //         "markPrice" => 1964.195,
        //         "markValue" => 39283900,
        //         "riskValue" => 39283900,
        //         "homeNotional" => 0.02,
        //         "foreignNotional" => -39.2839,
        //         "posState" => "",
        //         "posCost" => 39260000,
        //         "posCost2" => 39260000,
        //         "posCross" => 0,
        //         "posInit" => 13086667,
        //         "posComm" => 39261,
        //         "posLoss" => 0,
        //         "posMargin" => 13125928,
        //         "posMaint" => 435787,
        //         "posAllowance" => 0,
        //         "taxableMargin" => 0,
        //         "initMargin" => 0,
        //         "maintMargin" => 13149828,
        //         "sessionMargin" => 0,
        //         "targetExcessMargin" => 0,
        //         "varMargin" => 0,
        //         "realisedGrossPnl" => 0,
        //         "realisedTax" => 0,
        //         "realisedPnl" => -26500,
        //         "unrealisedGrossPnl" => 23900,
        //         "longBankrupt" => 0,
        //         "shortBankrupt" => 0,
        //         "taxBase" => 0,
        //         "indicativeTaxRate" => null,
        //         "indicativeTax" => 0,
        //         "unrealisedTax" => 0,
        //         "unrealisedPnl" => 23900,
        //         "unrealisedPnlPcnt" => 0.0006,
        //         "unrealisedRoePcnt" => 0.0018,
        //         "simpleQty" => null,
        //         "simpleCost" => null,
        //         "simpleValue" => null,
        //         "simplePnl" => null,
        //         "simplePnlPcnt" => null,
        //         "avgCostPrice" => 1963,
        //         "avgEntryPrice" => 1963,
        //         "breakEvenPrice" => 1964.35,
        //         "marginCallPrice" => 1328.5,
        //         "liquidationPrice" => 1328.5,
        //         "bankruptPrice" => 1308.7,
        //         "timestamp" => "2022-05-21T04:35:16.397Z",
        //         "lastPrice" => 1964.195,
        //         "lastValue" => 39283900
        //     }
        //
        $market = $this->safe_market($this->safe_string($position, 'symbol'), $market);
        $symbol = $market['symbol'];
        $datetime = $this->safe_string($position, 'timestamp');
        $crossMargin = $this->safe_value($position, 'crossMargin');
        $marginMode = ($crossMargin === true) ? 'cross' : 'isolated';
        $notionalString = Precise::string_abs($this->safe_string_2($position, 'foreignNotional', 'homeNotional'));
        $settleCurrencyCode = $this->safe_string($market, 'settle');
        $maintenanceMargin = $this->convert_to_real_amount($settleCurrencyCode, $this->safe_string($position, 'maintMargin'));
        $unrealisedPnl = $this->convert_to_real_amount($settleCurrencyCode, $this->safe_string($position, 'unrealisedPnl'));
        $contracts = $this->parse_number(Precise::string_abs($this->safe_string($position, 'currentQty')));
        $contractSize = $this->safe_number($market, 'contractSize');
        $side = null;
        $homeNotional = $this->safe_string($position, 'homeNotional');
        if ($homeNotional !== null) {
            if ($homeNotional[0] === '-') {
                $side = 'short';
            } else {
                $side = 'long';
            }
        }
        return $this->safe_position(array(
            'info' => $position,
            'id' => $this->safe_string($position, 'account'),
            'symbol' => $symbol,
            'timestamp' => $this->parse8601($datetime),
            'datetime' => $datetime,
            'lastUpdateTimestamp' => null,
            'hedged' => null,
            'side' => $side,
            'contracts' => $contracts,
            'contractSize' => $contractSize,
            'entryPrice' => $this->safe_number($position, 'avgEntryPrice'),
            'markPrice' => $this->safe_number($position, 'markPrice'),
            'lastPrice' => null,
            'notional' => $this->parse_number($notionalString),
            'leverage' => $this->safe_number($position, 'leverage'),
            'collateral' => null,
            'initialMargin' => $this->safe_number($position, 'initMargin'),
            'initialMarginPercentage' => $this->safe_number($position, 'initMarginReq'),
            'maintenanceMargin' => $maintenanceMargin,
            'maintenanceMarginPercentage' => $this->safe_number($position, 'maintMarginReq'),
            'unrealizedPnl' => $unrealisedPnl,
            'liquidationPrice' => $this->safe_number($position, 'liquidationPrice'),
            'marginMode' => $marginMode,
            'marginRatio' => null,
            'percentage' => $this->safe_number($position, 'unrealisedPnlPcnt'),
            'stopLossPrice' => null,
            'takeProfitPrice' => null,
        ));
    }

    public function withdraw(string $code, float $amount, string $address, ?string $tag = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $amount, $address, $tag, $params) {
            /**
             * make a withdrawal
             *
             * @see https://www.bitmex.com/api/explorer/#!/User/User_requestWithdrawal
             *
             * @param {string} $code unified $currency $code
             * @param {float} $amount the $amount to withdraw
             * @param {string} $address the $address to withdraw to
             * @param {string} $tag
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structure~
             */
            list($tag, $params) = $this->handle_withdraw_tag_and_params($tag, $params);
            $this->check_address($address);
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            $qty = $this->convert_from_real_amount($code, $amount);
            $networkCode = null;
            list($networkCode, $params) = $this->handle_network_code_and_params($params);
            $request = array(
                'currency' => $currency['id'],
                'amount' => $qty,
                'address' => $address,
                'network' => $this->network_code_to_id($networkCode, $currency['code']),
                // 'otpToken' => '123456', // requires if two-factor auth (OTP) is enabled
                // 'fee' => 0.001, // bitcoin network fee
            );
            if ($this->twofa !== null) {
                $request['otpToken'] = $this->totp($this->twofa);
            }
            $response = Async\await($this->privatePostUserRequestWithdrawal ($this->extend($request, $params)));
            //
            //     {
            //         "transactID" => "3aece414-bb29-76c8-6c6d-16a477a51a1e",
            //         "account" => 1403035,
            //         "currency" => "USDt",
            //         "network" => "tron",
            //         "transactType" => "Withdrawal",
            //         "amount" => -11000000,
            //         "fee" => 1000000,
            //         "transactStatus" => "Pending",
            //         "address" => "TAf5JxcAQQsC2Nm2zu21XE2iDtnisxPo1x",
            //         "tx" => "",
            //         "text" => "",
            //         "transactTime" => "2022-12-16T07:37:06.500Z",
            //         "timestamp" => "2022-12-16T07:37:06.500Z",
            //     }
            //
            return $this->parse_transaction($response, $currency);
        }) ();
    }

    public function fetch_funding_rates(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetch the funding rate for multiple markets
             *
             * @see https://www.bitmex.com/api/explorer/#!/Instrument/Instrument_getActiveAndIndices
             *
             * @param {string[]|null} $symbols list of unified $market $symbols
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=funding-rates-structure funding rate structures~, indexed by $market $symbols
             */
            Async\await($this->load_markets());
            $response = Async\await($this->publicGetInstrumentActiveAndIndices ($params));
            // same $response "fetchMarkets"
            $filteredResponse = array();
            for ($i = 0; $i < count($response); $i++) {
                $item = $response[$i];
                $marketId = $this->safe_string($item, 'symbol');
                $market = $this->safe_market($marketId);
                $swap = $this->safe_bool($market, 'swap', false);
                if ($swap) {
                    $filteredResponse[] = $item;
                }
            }
            $symbols = $this->market_symbols($symbols);
            $result = $this->parse_funding_rates($filteredResponse);
            return $this->filter_by_array($result, 'symbol', $symbols);
        }) ();
    }

    public function parse_funding_rate($contract, ?array $market = null): array {
        // see response sample under "fetchMarkets" because same endpoint is being used here
        $datetime = $this->safe_string($contract, 'timestamp');
        $marketId = $this->safe_string($contract, 'symbol');
        $fundingDatetime = $this->safe_string($contract, 'fundingTimestamp');
        return array(
            'info' => $contract,
            'symbol' => $this->safe_symbol($marketId, $market),
            'markPrice' => $this->safe_number($contract, 'markPrice'),
            'indexPrice' => null,
            'interestRate' => null,
            'estimatedSettlePrice' => $this->safe_number($contract, 'indicativeSettlePrice'),
            'timestamp' => $this->parse8601($datetime),
            'datetime' => $datetime,
            'fundingRate' => $this->safe_number($contract, 'fundingRate'),
            'fundingTimestamp' => $this->parse8601($fundingDatetime),
            'fundingDatetime' => $fundingDatetime,
            'nextFundingRate' => $this->safe_number($contract, 'indicativeFundingRate'),
            'nextFundingTimestamp' => null,
            'nextFundingDatetime' => null,
            'previousFundingRate' => null,
            'previousFundingTimestamp' => null,
            'previousFundingDatetime' => null,
            'interval' => null,
        );
    }

    public function fetch_funding_rate_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * Fetches the history of funding rates
             *
             * @see https://www.bitmex.com/api/explorer/#!/Funding/Funding_get
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the funding rate history for
             * @param {int} [$since] timestamp in ms of the earliest funding rate to fetch
             * @param {int} [$limit] the maximum amount of ~@link https://docs.ccxt.com/#/?id=funding-rate-history-structure funding rate structures~ to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] timestamp in ms for ending date filter
             * @param {bool} [$params->reverse] if true, will sort results newest first
             * @param {int} [$params->start] starting point for results
             * @param {string} [$params->columns] array of column names to fetch in info, if omitted, will return all columns
             * @param {string} [$params->filter] generic table filter, send json key/value pairs, such as array("key" => "value"), you can key on individual fields, and do more advanced querying on timestamps, see the {@link https://www.bitmex.com/app/restAPI#Timestamp-Filters timestamp docs} for more details
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=funding-rate-history-structure funding rate structures~
             */
            Async\await($this->load_markets());
            $request = array();
            $market = null;
            if (is_array($this->currencies) && array_key_exists($symbol, $this->currencies)) {
                $code = $this->currency($symbol);
                $request['symbol'] = $code['id'];
            } elseif ($symbol !== null) {
                $splitSymbol = explode(':', $symbol);
                $splitSymbolLength = count($splitSymbol);
                $timeframes = array( 'nearest', 'daily', 'weekly', 'monthly', 'quarterly', 'biquarterly', 'perpetual' );
                if (($splitSymbolLength > 1) && $this->in_array($splitSymbol[1], $timeframes)) {
                    $code = $this->currency($splitSymbol[0]);
                    $symbol = $code['id'] . ':' . $splitSymbol[1];
                    $request['symbol'] = $symbol;
                } else {
                    $market = $this->market($symbol);
                    $request['symbol'] = $market['id'];
                }
            }
            if ($since !== null) {
                $request['startTime'] = $this->iso8601($since);
            }
            if ($limit !== null) {
                $request['count'] = $limit;
            }
            $until = $this->safe_integer($params, 'until');
            $params = $this->omit($params, array( 'until' ));
            if ($until !== null) {
                $request['endTime'] = $this->iso8601($until);
            }
            if (($since === null) && ($until === null)) {
                $request['reverse'] = true;
            }
            $response = Async\await($this->publicGetFunding ($this->extend($request, $params)));
            //
            //    array(
            //        {
            //            "timestamp" => "2016-05-07T12:00:00.000Z",
            //            "symbol" => "ETHXBT",
            //            "fundingInterval" => "2000-01-02T00:00:00.000Z",
            //            "fundingRate" => 0.0010890000000000001,
            //            "fundingRateDaily" => 0.0010890000000000001
            //        }
            //    )
            //
            return $this->parse_funding_rate_histories($response, $market, $since, $limit);
        }) ();
    }

    public function parse_funding_rate_history($info, ?array $market = null) {
        //
        //    {
        //        "timestamp" => "2016-05-07T12:00:00.000Z",
        //        "symbol" => "ETHXBT",
        //        "fundingInterval" => "2000-01-02T00:00:00.000Z",
        //        "fundingRate" => 0.0010890000000000001,
        //        "fundingRateDaily" => 0.0010890000000000001
        //    }
        //
        $marketId = $this->safe_string($info, 'symbol');
        $datetime = $this->safe_string($info, 'timestamp');
        return array(
            'info' => $info,
            'symbol' => $this->safe_symbol($marketId, $market),
            'fundingRate' => $this->safe_number($info, 'fundingRate'),
            'timestamp' => $this->parse8601($datetime),
            'datetime' => $datetime,
        );
    }

    public function set_leverage(int $leverage, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($leverage, $symbol, $params) {
            /**
             * set the level of $leverage for a $market
             *
             * @see https://www.bitmex.com/api/explorer/#!/Position/Position_updateLeverage
             *
             * @param {float} $leverage the rate of $leverage
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} response from the exchange
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' setLeverage() requires a $symbol argument');
            }
            if (($leverage < 0.01) || ($leverage > 100)) {
                throw new BadRequest($this->id . ' $leverage should be between 0.01 and 100');
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            if ($market['type'] !== 'swap' && $market['type'] !== 'future') {
                throw new BadSymbol($this->id . ' setLeverage() supports future and swap contracts only');
            }
            $request = array(
                'symbol' => $market['id'],
                'leverage' => $leverage,
            );
            return Async\await($this->privatePostPositionLeverage ($this->extend($request, $params)));
        }) ();
    }

    public function set_margin_mode(string $marginMode, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($marginMode, $symbol, $params) {
            /**
             * set margin mode to 'cross' or 'isolated'
             *
             * @see https://www.bitmex.com/api/explorer/#!/Position/Position_isolateMargin
             *
             * @param {string} $marginMode 'cross' or 'isolated'
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} response from the exchange
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' setMarginMode() requires a $symbol argument');
            }
            $marginMode = strtolower($marginMode);
            if ($marginMode !== 'isolated' && $marginMode !== 'cross') {
                throw new BadRequest($this->id . ' setMarginMode() $marginMode argument should be isolated or cross');
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            if (($market['type'] !== 'swap') && ($market['type'] !== 'future')) {
                throw new BadSymbol($this->id . ' setMarginMode() supports swap and future contracts only');
            }
            $enabled = ($marginMode === 'cross') ? false : true;
            $request = array(
                'symbol' => $market['id'],
                'enabled' => $enabled,
            );
            return Async\await($this->privatePostPositionIsolate ($this->extend($request, $params)));
        }) ();
    }

    public function fetch_deposit_address(string $code, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $params) {
            /**
             * fetch the deposit address for a $currency associated with this account
             *
             * @see https://www.bitmex.com/api/explorer/#!/User/User_getDepositAddress
             *
             * @param {string} $code unified $currency $code
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->network] deposit chain, can view all chains via $this->publicGetWalletAssets, default is eth, unless the $currency has a default chain within $this->options['networks']
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=address-structure address structure~
             */
            Async\await($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 $params["network"]');
            }
            $currency = $this->currency($code);
            $params = $this->omit($params, 'network');
            $request = array(
                'currency' => $currency['id'],
                'network' => $this->network_code_to_id($networkCode, $currency['code']),
            );
            $response = Async\await($this->privateGetUserDepositAddress ($this->extend($request, $params)));
            //
            //    '"bc1qmex3puyrzn2gduqcnlu70c2uscpyaa9nm2l2j9le2lt2wkgmw33sy7ndjg"'
            //
            return array(
                'info' => $response,
                'currency' => $code,
                'network' => $networkCode,
                'address' => str_replace('"', '', $response->replace ('"', '')), // Done twice because some languages only replace the first instance
                'tag' => null,
            );
        }) ();
    }

    public function parse_deposit_withdraw_fee($fee, ?array $currency = null) {
        //
        //    {
        //        "asset" => "XBT",
        //        "currency" => "XBt",
        //        "majorCurrency" => "XBT",
        //        "name" => "Bitcoin",
        //        "currencyType" => "Crypto",
        //        "scale" => "8",
        //        "enabled" => true,
        //        "isMarginCurrency" => true,
        //        "minDepositAmount" => "10000",
        //        "minWithdrawalAmount" => "1000",
        //        "maxWithdrawalAmount" => "100000000000000",
        //        "networks" => array(
        //            {
        //                "asset" => "btc",
        //                "tokenAddress" => '',
        //                "depositEnabled" => true,
        //                "withdrawalEnabled" => true,
        //                "withdrawalFee" => "20000",
        //                "minFee" => "20000",
        //                "maxFee" => "10000000"
        //            }
        //        )
        //    }
        //
        $networks = $this->safe_value($fee, 'networks', array());
        $networksLength = count($networks);
        $result = array(
            'info' => $fee,
            'withdraw' => array(
                'fee' => null,
                'percentage' => null,
            ),
            'deposit' => array(
                'fee' => null,
                'percentage' => null,
            ),
            'networks' => array(),
        );
        if ($networksLength !== 0) {
            $scale = $this->safe_string($fee, 'scale');
            $precision = $this->parse_precision($scale);
            for ($i = 0; $i < $networksLength; $i++) {
                $network = $networks[$i];
                $networkId = $this->safe_string($network, 'asset');
                $currencyCode = $this->safe_string($currency, 'code');
                $networkCode = $this->network_id_to_code($networkId, $currencyCode);
                $withdrawalFeeId = $this->safe_string($network, 'withdrawalFee');
                $withdrawalFee = $this->parse_number(Precise::string_mul($withdrawalFeeId, $precision));
                $result['networks'][$networkCode] = array(
                    'deposit' => array( 'fee' => null, 'percentage' => null ),
                    'withdraw' => array( 'fee' => $withdrawalFee, 'percentage' => false ),
                );
                if ($networksLength === 1) {
                    $result['withdraw']['fee'] = $withdrawalFee;
                    $result['withdraw']['percentage'] = false;
                }
            }
        }
        return $result;
    }

    public function fetch_deposit_withdraw_fees(?array $codes = null, $params = array ()) {
        return Async\async(function () use ($codes, $params) {
            /**
             * fetch deposit and withdraw fees
             *
             * @see https://www.bitmex.com/api/explorer/#!/Wallet/Wallet_getAssetsConfig
             *
             * @param {string[]|null} $codes list of unified currency $codes
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a list of ~@link https://docs.ccxt.com/#/?id=fee-structure fee structures~
             */
            Async\await($this->load_markets());
            $assets = Async\await($this->publicGetWalletAssets ($params));
            //
            //    array(
            //        {
            //            "asset" => "XBT",
            //            "currency" => "XBt",
            //            "majorCurrency" => "XBT",
            //            "name" => "Bitcoin",
            //            "currencyType" => "Crypto",
            //            "scale" => "8",
            //            "enabled" => true,
            //            "isMarginCurrency" => true,
            //            "minDepositAmount" => "10000",
            //            "minWithdrawalAmount" => "1000",
            //            "maxWithdrawalAmount" => "100000000000000",
            //            "networks" => array(
            //                array(
            //                    "asset" => "btc",
            //                    "tokenAddress" => '',
            //                    "depositEnabled" => true,
            //                    "withdrawalEnabled" => true,
            //                    "withdrawalFee" => "20000",
            //                    "minFee" => "20000",
            //                    "maxFee" => "10000000"
            //                }
            //            )
            //        ),
            //        ...
            //    )
            //
            return $this->parse_deposit_withdraw_fees($assets, $codes, 'asset');
        }) ();
    }

    public function calculate_rate_limiter_cost($api, $method, $path, $params, $config = array ()) {
        $isAuthenticated = $this->check_required_credentials(false);
        $cost = $this->safe_value($config, 'cost', 1);
        if ($cost !== 1) { // trading endpoints
            if ($isAuthenticated) {
                return $cost;
            } else {
                return 20;
            }
        }
        return $cost;
    }

    public function fetch_liquidations(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * retrieves the public liquidations of a trading pair
             *
             * @see https://www.bitmex.com/api/explorer/#!/Liquidation/Liquidation_get
             *
             * @param {string} $symbol unified CCXT $market $symbol
             * @param {int} [$since] the earliest time in ms to fetch liquidations for
             * @param {int} [$limit] the maximum number of liquidation structures to retrieve
             * @param {array} [$params] exchange specific parameters for the bitmex api endpoint
             * @param {int} [$params->until] timestamp in ms of the latest liquidation
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {array} an array of ~@link https://docs.ccxt.com/#/?id=liquidation-structure liquidation structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchLiquidations', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_dynamic('fetchLiquidations', $symbol, $since, $limit, $params));
            }
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
            );
            if ($since !== null) {
                $request['startTime'] = $since;
            }
            if ($limit !== null) {
                $request['count'] = $limit;
            }
            list($request, $params) = $this->handle_until_option('endTime', $request, $params);
            $response = Async\await($this->publicGetLiquidation ($this->extend($request, $params)));
            //
            //     array(
            //         {
            //             "orderID" => "string",
            //             "symbol" => "string",
            //             "side" => "string",
            //             "price" => 0,
            //             "leavesQty" => 0
            //         }
            //     )
            //
            return $this->parse_liquidations($response, $market, $since, $limit);
        }) ();
    }

    public function parse_liquidation($liquidation, ?array $market = null) {
        //
        //     {
        //         "orderID" => "string",
        //         "symbol" => "string",
        //         "side" => "string",
        //         "price" => 0,
        //         "leavesQty" => 0
        //     }
        //
        $marketId = $this->safe_string($liquidation, 'symbol');
        return $this->safe_liquidation(array(
            'info' => $liquidation,
            'symbol' => $this->safe_symbol($marketId, $market),
            'contracts' => null,
            'contractSize' => $this->safe_number($market, 'contractSize'),
            'price' => $this->safe_number($liquidation, 'price'),
            'side' => $this->safe_string_lower($liquidation, 'side'),
            'baseValue' => null,
            'quoteValue' => null,
            'timestamp' => null,
            'datetime' => null,
        ));
    }

    public function handle_errors(int $code, string $reason, string $url, string $method, array $headers, string $body, $response, $requestHeaders, $requestBody) {
        if ($response === null) {
            return null;
        }
        if ($code === 429) {
            throw new DDoSProtection($this->id . ' ' . $body);
        }
        if ($code >= 400) {
            $error = $this->safe_value($response, 'error', array());
            $message = $this->safe_string($error, 'message');
            $feedback = $this->id . ' ' . $body;
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $message, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $message, $feedback);
            if ($code === 400) {
                throw new BadRequest($feedback);
            }
            throw new ExchangeError($feedback); // unknown $message
        }
        return null;
    }

    public function nonce() {
        return $this->milliseconds();
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $query = '/api/' . $this->version . '/' . $path;
        if ($method === 'GET') {
            if ($params) {
                $query .= '?' . $this->urlencode($params);
            }
        } else {
            $format = $this->safe_string($params, '_format');
            if ($format !== null) {
                $query .= '?' . $this->urlencode(array( '_format' => $format ));
                $params = $this->omit($params, '_format');
            }
        }
        $url = $this->urls['api'][$api] . $query;
        $isAuthenticated = $this->check_required_credentials(false);
        if ($api === 'private' || ($api === 'public' && $isAuthenticated)) {
            $this->check_required_credentials();
            $auth = $method . $query;
            $expires = $this->safe_integer($this->options, 'api-expires');
            $headers = array(
                'Content-Type' => 'application/json',
                'api-key' => $this->apiKey,
            );
            $expires = $this->sum($this->seconds(), $expires);
            $stringExpires = (string) $expires;
            $auth .= $stringExpires;
            $headers['api-expires'] = $stringExpires;
            if ($method === 'POST' || $method === 'PUT' || $method === 'DELETE') {
                if ($params) {
                    $body = $this->json($params);
                    $auth .= $body;
                }
            }
            $headers['api-signature'] = $this->hmac($this->encode($auth), $this->encode($this->secret), 'sha256');
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }
}
