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

class krakenfutures extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'krakenfutures',
            'name' => 'Kraken Futures',
            'countries' => array( 'US' ),
            'version' => 'v3',
            'userAgent' => null,
            'rateLimit' => 600,
            'pro' => true,
            'has' => array(
                'CORS' => null,
                'spot' => false,
                'margin' => false,
                'swap' => true,
                'future' => true,
                'option' => false,
                'cancelAllOrders' => true,
                'cancelAllOrdersAfter' => true,
                'cancelOrder' => true,
                'cancelOrders' => true,
                'createMarketOrder' => false,
                'createOrder' => true,
                'createPostOnlyOrder' => true,
                'createReduceOnlyOrder' => true,
                'createStopLimitOrder' => true,
                'createStopMarketOrder' => true,
                'createStopOrder' => true,
                'createTriggerOrder' => true,
                'editOrder' => true,
                'fetchBalance' => true,
                'fetchBorrowRateHistories' => false,
                'fetchBorrowRateHistory' => false,
                'fetchCanceledOrders' => true,
                'fetchClosedOrders' => true, // https://support.kraken.com/hc/en-us/articles/360058243651-Historical-orders
                'fetchCrossBorrowRate' => false,
                'fetchCrossBorrowRates' => false,
                'fetchCurrencies' => false,
                'fetchDepositAddress' => false,
                'fetchDepositAddresses' => false,
                'fetchDepositAddressesByNetwork' => false,
                'fetchFundingHistory' => null,
                'fetchFundingRate' => 'emulated',
                'fetchFundingRateHistory' => true,
                'fetchFundingRates' => true,
                'fetchIndexOHLCV' => false,
                'fetchIsolatedBorrowRate' => false,
                'fetchIsolatedBorrowRates' => false,
                'fetchIsolatedPositions' => false,
                'fetchLeverage' => true,
                'fetchLeverages' => true,
                'fetchLeverageTiers' => true,
                'fetchMarketLeverageTiers' => 'emulated',
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => true,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenOrders' => true,
                'fetchOrder' => false,
                'fetchOrderBook' => true,
                'fetchOrders' => false,
                'fetchPositions' => true,
                'fetchPremiumIndexOHLCV' => false,
                'fetchTickers' => true,
                'fetchTrades' => true,
                'sandbox' => true,
                'setLeverage' => true,
                'setMarginMode' => false,
                'transfer' => true,
            ),
            'urls' => array(
                'test' => array(
                    'public' => 'https://demo-futures.kraken.com/derivatives/api/',
                    'private' => 'https://demo-futures.kraken.com/derivatives/api/',
                    'charts' => 'https://demo-futures.kraken.com/api/charts/',
                    'history' => 'https://demo-futures.kraken.com/api/history/',
                    'www' => 'https://demo-futures.kraken.com',
                ),
                'logo' => 'https://user-images.githubusercontent.com/24300605/81436764-b22fd580-9172-11ea-9703-742783e6376d.jpg',
                'api' => array(
                    'charts' => 'https://futures.kraken.com/api/charts/',
                    'history' => 'https://futures.kraken.com/api/history/',
                    'feeschedules' => 'https://futures.kraken.com/api/feeschedules/',
                    'public' => 'https://futures.kraken.com/derivatives/api/',
                    'private' => 'https://futures.kraken.com/derivatives/api/',
                ),
                'www' => 'https://futures.kraken.com/',
                'doc' => array(
                    'https://docs.kraken.com/api/docs/futures-api/trading/market-data/',
                ),
                'fees' => 'https://support.kraken.com/hc/en-us/articles/360022835771-Transaction-fees-and-rebates-for-Kraken-Futures',
                'referral' => null,
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'feeschedules',
                        'instruments',
                        'orderbook',
                        'tickers',
                        'history',
                        'historicalfundingrates',
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'feeschedules/volumes',
                        'openpositions',
                        'notifications',
                        'accounts',
                        'openorders',
                        'recentorders',
                        'fills',
                        'transfers',
                        'leveragepreferences',
                        'pnlpreferences',
                        'assignmentprogram/current',
                        'assignmentprogram/history',
                    ),
                    'post' => array(
                        'sendorder',
                        'editorder',
                        'cancelorder',
                        'transfer',
                        'batchorder',
                        'cancelallorders',
                        'cancelallordersafter',
                        'withdrawal',                              // for futures wallet -> kraken spot wallet
                        'assignmentprogram/add',
                        'assignmentprogram/delete',
                    ),
                    'put' => array(
                        'leveragepreferences',
                        'pnlpreferences',
                    ),
                ),
                'charts' => array(
                    'get' => array(
                        '{price_type}/{symbol}/{interval}',
                    ),
                ),
                'history' => array(
                    'get' => array(
                        'orders',
                        'executions',
                        'triggers',
                        'accountlogcsv',
                        'account-log',
                        'market/{symbol}/orders',
                        'market/{symbol}/executions',
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'tierBased' => true,
                    'percentage' => true,
                    'taker' => $this->parse_number('0.0005'),
                    'maker' => $this->parse_number('0.0002'),
                    'tiers' => array(
                        'taker' => array(
                            array( $this->parse_number('0'), $this->parse_number('0.0005') ),
                            array( $this->parse_number('100000'), $this->parse_number('0.0004') ),
                            array( $this->parse_number('1000000'), $this->parse_number('0.0003') ),
                            array( $this->parse_number('5000000'), $this->parse_number('0.00025') ),
                            array( $this->parse_number('10000000'), $this->parse_number('0.0002') ),
                            array( $this->parse_number('20000000'), $this->parse_number('0.00015') ),
                            array( $this->parse_number('50000000'), $this->parse_number('0.000125') ),
                            array( $this->parse_number('100000000'), $this->parse_number('0.0001') ),
                        ),
                        'maker' => array(
                            array( $this->parse_number('0'), $this->parse_number('0.0002') ),
                            array( $this->parse_number('100000'), $this->parse_number('0.0015') ),
                            array( $this->parse_number('1000000'), $this->parse_number('0.000125') ),
                            array( $this->parse_number('5000000'), $this->parse_number('0.0001') ),
                            array( $this->parse_number('10000000'), $this->parse_number('0.000075') ),
                            array( $this->parse_number('20000000'), $this->parse_number('0.00005') ),
                            array( $this->parse_number('50000000'), $this->parse_number('0.000025') ),
                            array( $this->parse_number('100000000'), $this->parse_number('0') ),
                        ),
                    ),
                ),
            ),
            'exceptions' => array(
                'exact' => array(
                    'apiLimitExceeded' => '\\ccxt\\RateLimitExceeded',
                    'marketUnavailable' => '\\ccxt\\ContractUnavailable',
                    'requiredArgumentMissing' => '\\ccxt\\BadRequest',
                    'unavailable' => '\\ccxt\\ExchangeNotAvailable',
                    'authenticationError' => '\\ccxt\\AuthenticationError',
                    'accountInactive' => '\\ccxt\\ExchangeError',              // When account has no trade history / no order history. Should this error be ignored in some cases?
                    'invalidAccount' => '\\ccxt\\BadRequest',                  // the fromAccount or the toAccount are invalid
                    'invalidAmount' => '\\ccxt\\BadRequest',
                    'insufficientFunds' => '\\ccxt\\InsufficientFunds',
                    'Bad Request' => '\\ccxt\\BadRequest',                     // The URL contains invalid characters. (Please encode the json URL parameter)
                    'Unavailable' => '\\ccxt\\ExchangeNotAvailable',              // https://github.com/ccxt/ccxt/issues/24338
                    'invalidUnit' => '\\ccxt\\BadRequest',
                    'Json Parse Error' => '\\ccxt\\ExchangeError',
                    'nonceBelowThreshold' => '\\ccxt\\InvalidNonce',
                    'nonceDuplicate' => '\\ccxt\\InvalidNonce',
                    'notFound' => '\\ccxt\\BadRequest',
                    'Server Error' => '\\ccxt\\ExchangeError',
                    'unknownError' => '\\ccxt\\ExchangeError',
                ),
                'broad' => array(
                    'invalidArgument' => '\\ccxt\\BadRequest',
                    'nonceBelowThreshold' => '\\ccxt\\InvalidNonce',
                    'nonceDuplicate' => '\\ccxt\\InvalidNonce',
                ),
            ),
            'precisionMode' => TICK_SIZE,
            'options' => array(
                'access' => array(
                    'history' => array(
                        'GET' => array(
                            'orders' => 'private',
                            'executions' => 'private',
                            'triggers' => 'private',
                            'accountlogcsv' => 'private',
                            'account-log' => 'private',
                        ),
                    ),
                ),
                'settlementCurrencies' => array(
                    'flex' => array( 'USDT', 'BTC', 'USD', 'GBP', 'EUR', 'USDC' ),
                ),
                'symbol' => array(
                    'quoteIds' => array( 'USD', 'XBT' ),
                    'reversed' => false,
                ),
                'versions' => array(
                    'public' => array(
                        'GET' => array(
                            'historicalfundingrates' => 'v4',
                        ),
                    ),
                    'charts' => array(
                        'GET' => array(
                            '{price_type}/{symbol}/{interval}' => 'v1',
                        ),
                    ),
                    'history' => array(
                        'GET' => array(
                            'orders' => 'v2',
                            'executions' => 'v2',
                            'triggers' => 'v2',
                            'accountlogcsv' => 'v2',
                        ),
                    ),
                ),
                'fetchTrades' => array(
                    'method' => 'historyGetMarketSymbolExecutions', // historyGetMarketSymbolExecutions, publicGetHistory
                ),
            ),
            'features' => array(
                'default' => array(
                    'sandbox' => true,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => true,
                        'triggerPriceType' => array(
                            'last' => true,
                            'mark' => true,
                            'index' => true,
                        ),
                        'triggerDirection' => false,
                        'stopLossPrice' => true,
                        'takeProfitPrice' => true,
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => true,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'trailing' => false,
                        'leverage' => false,
                        'marketBuyByCost' => false,
                        'marketBuyRequiresPrice' => false,
                        'selfTradePrevention' => false,
                        'iceberg' => false,
                    ),
                    'createOrders' => array(
                        'max' => 100,
                    ),
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => null,
                        'daysBack' => null,
                        'untilDays' => 100000,
                        'symbolRequired' => false,
                    ),
                    'fetchOrder' => null,
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => null,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => null,
                    'fetchClosedOrders' => array(
                        'marginMode' => false,
                        'limit' => null,
                        'daysBack' => null,
                        'daysBackCanceled' => null,
                        'untilDays' => null,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOHLCV' => array(
                        'limit' => 5000,
                    ),
                ),
                'spot' => null,
                'swap' => array(
                    'linear' => array(
                        'extends' => 'default',
                    ),
                    'inverse' => array(
                        'extends' => 'default',
                    ),
                ),
                'future' => array(
                    'linear' => array(
                        'extends' => 'default',
                    ),
                    'inverse' => array(
                        'extends' => 'default',
                    ),
                ),
            ),
            'timeframes' => array(
                '1m' => '1m',
                '5m' => '5m',
                '15m' => '15m',
                '30m' => '30m',
                '1h' => '1h',
                '4h' => '4h',
                '12h' => '12h',
                '1d' => '1d',
                '1w' => '1w',
            ),
        ));
    }

    public function fetch_markets($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * Fetches the available trading markets from the exchange, Multi-collateral markets are returned markets, but can be settled in multiple $currencies
             *
             * @see https://docs.kraken.com/api/docs/futures-api/trading/get-$instruments
             *
             * @param {array} [$params] exchange specific $params
             * @return An array of $market structures
             */
            $response = Async\await($this->publicGetInstruments ($params));
            //
            //    {
            //        "result" => "success",
            //        "instruments" => array(
            //            {
            //                "symbol" => "fi_ethusd_180928",
            //                "type" => "futures_inverse", // futures_vanilla  // spot $index
            //                "underlying" => "rr_ethusd",
            //                "lastTradingTime" => "2018-09-28T15:00:00.000Z",
            //                "tickSize" => 0.1,
            //                "contractSize" => 1,
            //                "tradeable" => true,
            //                "marginLevels" => array(
            //                    array(
            //                        "contracts":0,
            //                        "initialMargin":0.02,
            //                        "maintenanceMargin":0.01
            //                    ),
            //                    array(
            //                        "contracts":250000,
            //                        "initialMargin":0.04,
            //                        "maintenanceMargin":0.02
            //                    ),
            //                    ...
            //                ),
            //                "isin" => "GB00JVMLMP88",
            //                "retailMarginLevels" => array(
            //                    array(
            //                        "contracts" => 0,
            //                        "initialMargin" => 0.5,
            //                        "maintenanceMargin" => 0.25
            //                    }
            //                ),
            //                "tags" => array(),
            //            ),
            //            {
            //                "symbol" => "in_xbtusd",
            //                "type" => "spot $index",
            //                "tradeable":false
            //            }
            //        )
            //        "serverTime" => "2018-07-19T11:32:39.433Z"
            //    }
            //
            $instruments = $this->safe_value($response, 'instruments', array());
            $result = array();
            for ($i = 0; $i < count($instruments); $i++) {
                $market = $instruments[$i];
                $id = $this->safe_string($market, 'symbol');
                $marketType = $this->safe_string($market, 'type');
                $type = null;
                $index = (mb_strpos($marketType, ' index') !== false);
                $linear = null;
                $inverse = null;
                $expiry = null;
                if (!$index) {
                    $linear = (mb_strpos($marketType, '_vanilla') !== false);
                    $inverse = !$linear;
                    $settleTime = $this->safe_string($market, 'lastTradingTime');
                    $type = ($settleTime === null) ? 'swap' : 'future';
                    $expiry = $this->parse8601($settleTime);
                } else {
                    $type = 'index';
                }
                $swap = ($type === 'swap');
                $future = ($type === 'future');
                $symbol = $id;
                $split = explode('_', $id);
                $splitMarket = $this->safe_string($split, 1);
                $baseId = mb_substr($splitMarket, 0, strlen($splitMarket) - 3 - 0);
                $quoteId = 'usd'; // always USD
                $base = $this->safe_currency_code($baseId);
                $quote = $this->safe_currency_code($quoteId);
                // $swap == perpetual
                $settle = null;
                $settleId = null;
                $cvtp = $this->safe_string($market, 'contractValueTradePrecision');
                $amountPrecision = $this->parse_number($this->integer_precision_to_amount($cvtp));
                $pricePrecision = $this->safe_number($market, 'tickSize');
                $contract = ($swap || $future || $index);
                $swapOrFutures = ($swap || $future);
                if ($swapOrFutures) {
                    $exchangeType = $this->safe_string($market, 'type');
                    if ($exchangeType === 'futures_inverse') {
                        $settle = $base;
                        $settleId = $baseId;
                        $inverse = true;
                    } else {
                        $settle = $quote;
                        $settleId = $quoteId;
                        $inverse = false;
                    }
                    $linear = !$inverse;
                    $symbol = $base . '/' . $quote . ':' . $settle;
                    if ($future) {
                        $symbol = $symbol . '-' . $this->yymmdd($expiry);
                    }
                }
                $result[] = array(
                    'id' => $id,
                    'symbol' => $symbol,
                    'base' => $base,
                    'quote' => $quote,
                    'settle' => $settle,
                    'baseId' => $baseId,
                    'quoteId' => $quoteId,
                    'settleId' => $settleId,
                    'type' => $type,
                    'spot' => false,
                    'margin' => false,
                    'swap' => $swap,
                    'future' => $future,
                    'option' => false,
                    'index' => $index,
                    'active' => $this->safe_bool($market, 'tradeable'),
                    'contract' => $contract,
                    'linear' => $linear,
                    'inverse' => $inverse,
                    'contractSize' => $this->safe_number($market, 'contractSize'),
                    'maintenanceMarginRate' => null,
                    'expiry' => $expiry,
                    'expiryDatetime' => $this->iso8601($expiry),
                    'strike' => null,
                    'optionType' => null,
                    'precision' => array(
                        'amount' => $amountPrecision,
                        'price' => $pricePrecision,
                    ),
                    'limits' => array(
                        'leverage' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'amount' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'price' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'cost' => array(
                            'min' => null,
                            'max' => null,
                        ),
                    ),
                    'created' => $this->parse8601($this->safe_string($market, 'openingDate')),
                    'info' => $market,
                );
            }
            $settlementCurrencies = $this->options['settlementCurrencies']['flex'];
            $currencies = array();
            for ($i = 0; $i < count($settlementCurrencies); $i++) {
                $code = $settlementCurrencies[$i];
                $currencies[] = array(
                    'id' => strtolower($code),
                    'numericId' => null,
                    'code' => $code,
                    'precision' => null,
                );
            }
            $this->currencies = $this->map_to_safe_map($this->deep_extend($currencies, $this->currencies));
            return $result;
        }) ();
    }

    public function fetch_order_book(string $symbol, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $limit, $params) {
            /**
             *
             * @see https://docs.kraken.com/api/docs/futures-api/trading/get-orderbook
             *
             * Fetches a list of open orders in a $market
             * @param {string} $symbol Unified $market $symbol
             * @param {int} [$limit] Not used by krakenfutures
             * @param {array} [$params] exchange specific $params
             * @return An ~@link https://docs.ccxt.com/#/?id=order-book-structure order book structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
            );
            $response = Async\await($this->publicGetOrderbook ($this->extend($request, $params)));
            //
            //    {
            //       "result" => "success",
            //       "serverTime" => "2016-02-25T09:45:53.818Z",
            //       "orderBook" => array(
            //          "bids" => array(
            //                array(
            //                    4213,
            //                    2000,
            //                ),
            //                array(
            //                    4210,
            //                    4000,
            //                ),
            //                ...
            //            ),
            //            "asks" => array(
            //                array(
            //                    4218,
            //                    4000,
            //                ),
            //                array(
            //                    4220,
            //                    5000,
            //                ),
            //                ...
            //            ),
            //        ),
            //    }
            //
            $timestamp = $this->parse8601($response['serverTime']);
            return $this->parse_order_book($response['orderBook'], $symbol, $timestamp);
        }) ();
    }

    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://docs.kraken.com/api/docs/futures-api/trading/get-$tickers
             *
             * @param {string[]} $symbols unified $symbols of the markets to fetch the ticker for, all market $tickers are returned if not assigned
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an array of ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structures~
             */
            Async\await($this->load_markets());
            $response = Async\await($this->publicGetTickers ($params));
            //
            //    {
            //        "result" => "success",
            //        "tickers" => array(
            //            array(
            //                "tag" => 'semiannual',  // 'month', 'quarter', "perpetual", "semiannual",
            //                "pair" => "ETH:USD",
            //                "symbol" => "fi_ethusd_220624",
            //                "markPrice" => "2925.72",
            //                "bid" => "2923.8",
            //                "bidSize" => "16804",
            //                "ask" => "2928.65",
            //                "askSize" => "1339",
            //                "vol24h" => "860493",
            //                "openInterest" => "3023363.00000000",
            //                "open24h" => "3021.25",
            //                "indexPrice" => "2893.71",
            //                "last" => "2942.25",
            //                "lastTime" => "2022-02-18T14:08:15.578Z",
            //                "lastSize" => "151",
            //                "suspended" => false
            //            ),
            //            array(
            //                "symbol" => "in_xbtusd", // "rr_xbtusd",
            //                "last" => "40411",
            //                "lastTime" => "2022-02-18T14:16:28.000Z"
            //            ),
            //            ...
            //        ),
            //        "serverTime" => "2022-02-18T14:16:29.440Z"
            //    }
            //
            $tickers = $this->safe_list($response, 'tickers');
            return $this->parse_tickers($tickers, $symbols);
        }) ();
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        //
        //    {
        //        "tag" => 'semiannual',  // 'month', 'quarter', "perpetual", "semiannual",
        //        "pair" => "ETH:USD",
        //        "symbol" => "fi_ethusd_220624",
        //        "markPrice" => "2925.72",
        //        "bid" => "2923.8",
        //        "bidSize" => "16804",
        //        "ask" => "2928.65",
        //        "askSize" => "1339",
        //        "vol24h" => "860493",
        //        "openInterest" => "3023363.00000000",
        //        "open24h" => "3021.25",
        //        "indexPrice" => "2893.71",
        //        "last" => "2942.25",
        //        "lastTime" => "2022-02-18T14:08:15.578Z",
        //        "lastSize" => "151",
        //        "suspended" => false
        //    }
        //
        //    {
        //        "symbol" => "in_xbtusd", // "rr_xbtusd",
        //        "last" => "40411",
        //        "lastTime" => "2022-02-18T14:16:28.000Z"
        //    }
        //
        $marketId = $this->safe_string($ticker, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $timestamp = $this->parse8601($this->safe_string($ticker, 'lastTime'));
        $open = $this->safe_string($ticker, 'open24h');
        $last = $this->safe_string($ticker, 'last');
        $change = Precise::string_sub($last, $open);
        $percentage = Precise::string_mul(Precise::string_div($change, $open), '100');
        $average = Precise::string_div(Precise::string_add($open, $last), '2');
        $volume = $this->safe_string($ticker, 'vol24h');
        $baseVolume = null;
        $quoteVolume = null;
        $isIndex = $this->safe_bool($market, 'index', false);
        if (!$isIndex) {
            if ($market['linear']) {
                $baseVolume = $volume;
            } elseif ($market['inverse']) {
                $quoteVolume = $volume;
            }
        }
        return $this->safe_ticker(array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => null,
            'low' => null,
            'bid' => $this->safe_string($ticker, 'bid'),
            'bidVolume' => $this->safe_string($ticker, 'bidSize'),
            'ask' => $this->safe_string($ticker, 'ask'),
            'askVolume' => $this->safe_string($ticker, 'askSize'),
            'vwap' => null,
            'open' => $open,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => $change,
            'percentage' => $percentage,
            'average' => $average,
            'baseVolume' => $baseVolume,
            'quoteVolume' => $quoteVolume,
            'markPrice' => $this->safe_string($ticker, 'markPrice'),
            'indexPrice' => $this->safe_string($ticker, 'indexPrice'),
            'info' => $ticker,
        ));
    }

    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) {
            /**
             *
             * @see https://docs.kraken.com/api/docs/futures-api/charts/candles
             *
             * fetches historical candlestick data containing the open, high, low, and close price, and the volume of a $market
             * @param {string} $symbol unified $symbol of the $market to fetch OHLCV data for
             * @param {string} $timeframe the length of time each candle represents
             * @param {int} [$since] timestamp in ms of the earliest candle to fetch
             * @param {int} [$limit] the maximum amount of $candles to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {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());
            $market = $this->market($symbol);
            $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, 5000));
            }
            $request = array(
                'symbol' => $market['id'],
                'price_type' => $this->safe_string($params, 'price', 'trade'),
                'interval' => $this->timeframes[$timeframe],
            );
            $params = $this->omit($params, 'price');
            if ($since !== null) {
                $duration = $this->parse_timeframe($timeframe);
                $request['from'] = $this->parse_to_int($since / 1000);
                if ($limit === null) {
                    $limit = 5000;
                }
                $limit = min ($limit, 5000);
                $toTimestamp = $this->sum($request['from'], $limit * $duration - 1);
                $currentTimestamp = $this->seconds();
                $request['to'] = min ($toTimestamp, $currentTimestamp);
            } elseif ($limit !== null) {
                $limit = min ($limit, 5000);
                $duration = $this->parse_timeframe($timeframe);
                $request['to'] = $this->seconds();
                $request['from'] = $this->parse_to_int($request['to'] - ($duration * $limit));
            }
            $response = Async\await($this->chartsGetPriceTypeSymbolInterval ($this->extend($request, $params)));
            //
            //    {
            //        "candles" => array(
            //            {
            //                "time" => 1645198500000,
            //                "open" => "309.15000000000",
            //                "high" => "309.15000000000",
            //                "low" => "308.70000000000",
            //                "close" => "308.85000000000",
            //                "volume" => 0
            //            }
            //        ),
            //        "more_candles" => true
            //    }
            //
            $candles = $this->safe_list($response, 'candles');
            return $this->parse_ohlcvs($candles, $market, $timeframe, $since, $limit);
        }) ();
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        //
        //    {
        //        "time" => 1645198500000,
        //        "open" => "309.15000000000",
        //        "high" => "309.15000000000",
        //        "low" => "308.70000000000",
        //        "close" => "308.85000000000",
        //        "volume" => 0
        //    }
        //
        return array(
            $this->safe_integer($ohlcv, 'time'),       // unix timestamp in milliseconds
            $this->safe_number($ohlcv, 'open'),        // open price
            $this->safe_number($ohlcv, 'high'),        // highest price
            $this->safe_number($ohlcv, 'low'),         // lowest price
            $this->safe_number($ohlcv, 'close'),       // close price
            $this->safe_number($ohlcv, 'volume'),      // trading volume, null for mark or index price
        );
    }

    public function fetch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             *
             * @see https://docs.kraken.com/api/docs/futures-api/trading/get-history
             * @see https://docs.kraken.com/api/docs/futures-api/history/get-public-execution-events
             *
             * Fetch a history of filled trades that this account has made
             * @param {string} $symbol Unified CCXT $market $symbol
             * @param {int} [$since] Timestamp in ms of earliest trade. Not used by krakenfutures except in combination with $params->until
             * @param {int} [$limit] Total number of trades, cannot exceed 100
             * @param {array} [$params] Exchange specific $params
             * @param {int} [$params->until] Timestamp in ms of latest trade
             * @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)
             * @param {string} [$params->method] The $method to use to fetch trades. Can be 'historyGetMarketSymbolExecutions' or 'publicGetHistory' default is 'historyGetMarketSymbolExecutions'
             * @return An array 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, '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'],
            );
            $method = null;
            list($method, $params) = $this->handle_option_and_params($params, 'fetchTrades', 'method', 'historyGetMarketSymbolExecutions');
            $rawTrades = null;
            $isFullHistoryEndpoint = ($method === 'historyGetMarketSymbolExecutions');
            if ($isFullHistoryEndpoint) {
                list($request, $params) = $this->handle_until_option('before', $request, $params);
                if ($since !== null) {
                    $request['since'] = $since;
                    $request['sort'] = 'asc';
                }
                if ($limit !== null) {
                    $request['count'] = $limit;
                }
                $response = Async\await($this->historyGetMarketSymbolExecutions ($this->extend($request, $params)));
                //
                //    {
                //        "elements" => array(
                //            {
                //                "uid" => "a5105030-f054-44cc-98ab-30d5cae96bef",
                //                "timestamp" => "1710150778607",
                //                "event" => {
                //                    "Execution" => array(
                //                        "execution" => array(
                //                            "uid" => "2d485b71-cd28-4a1e-9364-371a127550d2",
                //                            "makerOrder" => array(
                //                                "uid" => "0a25f66b-1109-49ec-93a3-d17bf9e9137e",
                //                                "tradeable" => "PF_XBTUSD",
                //                                "direction" => "Buy",
                //                                "quantity" => "0.26500",
                //                                "timestamp" => "1710150778570",
                //                                "limitPrice" => "71907",
                //                                "orderType" => "Post",
                //                                "reduceOnly" => false,
                //                                "lastUpdateTimestamp" => "1710150778570"
                //                            ),
                //                            "takerOrder" => array(
                //                                "uid" => "04de3ee0-9125-4960-bf8f-f63b577b6790",
                //                                "tradeable" => "PF_XBTUSD",
                //                                "direction" => "Sell",
                //                                "quantity" => "0.0002",
                //                                "timestamp" => "1710150778607",
                //                                "limitPrice" => "71187.00",
                //                                "orderType" => "Market",
                //                                "reduceOnly" => false,
                //                                "lastUpdateTimestamp" => "1710150778607"
                //                            ),
                //                            "timestamp" => "1710150778607",
                //                            "quantity" => "0.0002",
                //                            "price" => "71907",
                //                            "markPrice" => "71903.32715463147",
                //                            "limitFilled" => false,
                //                            "usdValue" => "14.38"
                //                        ),
                //                        "takerReducedQuantity" => ""
                //                    }
                //                }
                //            ),
                //            ... followed by older items
                //        ),
                //        "len" => "1000",
                //        "continuationToken" => "QTexMDE0OTe33NTcyXy8xNDIzAjc1NjY5MwI="
                //    }
                //
                $elements = $this->safe_list($response, 'elements', array());
                // we need to reverse the list to fix chronology
                $rawTrades = array();
                $length = count($elements);
                for ($i = 0; $i < $length; $i++) {
                    $index = $length - 1 - $i;
                    $element = $elements[$index];
                    $event = $this->safe_dict($element, 'event', array());
                    $executionContainer = $this->safe_dict($event, 'Execution', array());
                    $rawTrade = $this->safe_dict($executionContainer, 'execution', array());
                    $rawTrades[] = $rawTrade;
                }
            } else {
                list($request, $params) = $this->handle_until_option('lastTime', $request, $params);
                $response = Async\await($this->publicGetHistory ($this->extend($request, $params)));
                //
                //    {
                //        "result" => "success",
                //        "history" => array(
                //            array(
                //                "time" => "2022-03-18T04:55:37.692Z",
                //                "trade_id" => 100,
                //                "price" => 0.7921,
                //                "size" => 1068,
                //                "side" => "sell",
                //                "type" => "fill",
                //                "uid" => "6c5da0b0-f1a8-483f-921f-466eb0388265"
                //            ),
                //            ...
                //        ),
                //        "serverTime" => "2022-03-18T06:39:18.056Z"
                //    }
                //
                $rawTrades = $this->safe_list($response, 'history', array());
            }
            return $this->parse_trades($rawTrades, $market, $since, $limit);
        }) ();
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // fetchTrades (recent trades)
        //
        //    {
        //        "time" => "2019-02-14T09:25:33.920Z",
        //        "trade_id" => 100,
        //        "price" => 3574,
        //        "size" => 100,
        //        "side" => "buy",
        //        "type" => "fill" // fill, liquidation, assignment, termination
        //        "uid" => "11c3d82c-9e70-4fe9-8115-f643f1b162d4"
        //    }
        //
        // fetchTrades (executions history)
        //
        //    {
        //        "timestamp" => "1710152516830",
        //        "price" => "71927.0",
        //        "quantity" => "0.0695",
        //        "markPrice" => "71936.38701675525",
        //        "limitFilled" => true,
        //        "usdValue" => "4998.93",
        //        "uid" => "116ae634-253f-470b-bd20-fa9d429fb8b1",
        //        "makerOrder" => array( "uid" => "17bfe4de-c01e-4938-926c-617d2a2d0597", "tradeable" => "PF_XBTUSD", "direction" => "Buy", "quantity" => "0.0695", "timestamp" => "1710152515836", "limitPrice" => "71927.0", "orderType" => "Post", "reduceOnly" => false, "lastUpdateTimestamp" => "1710152515836" ),
        //        "takerOrder" => array( "uid" => "d3e437b4-aa70-4108-b5cf-b1eecb9845b5", "tradeable" => "PF_XBTUSD", "direction" => "Sell", "quantity" => "0.940100", "timestamp" => "1710152516830", "limitPrice" => "71915", "orderType" => "IoC", "reduceOnly" => false, "lastUpdateTimestamp" => "1710152516830" )
        //    }
        //
        // fetchMyTrades (private)
        //
        //    {
        //        "fillTime" => "2016-02-25T09:47:01.000Z",
        //        "order_id" => "c18f0c17-9971-40e6-8e5b-10df05d422f0",
        //        "fill_id" => "522d4e08-96e7-4b44-9694-bfaea8fe215e",
        //        "cliOrdId" => "d427f920-ec55-4c18-ba95-5fe241513b30",     // OPTIONAL
        //        "symbol" => "fi_xbtusd_180615",
        //        "side" => "buy",
        //        "size" => 2000,
        //        "price" => 4255,
        //        "fillType" => "maker"                                     // $taker, takerAfterEdit, maker, liquidation, assignee
        //    }
        //
        // execution report (createOrder, editOrder)
        //
        //    {
        //        "executionId" => "e1ec9f63-2338-4c44-b40a-43486c6732d7",
        //        "price" => 7244.5,
        //        "amount" => 10,
        //        "orderPriorEdit" => null,
        //        "orderPriorExecution" => array(
        //            "orderId" => "61ca5732-3478-42fe-8362-abbfd9465294",
        //            "cliOrdId" => null,
        //            "type" => "lmt",
        //            "symbol" => "pi_xbtusd",
        //            "side" => "buy",
        //            "quantity" => 10,
        //            "filled" => 0,
        //            "limitPrice" => 7500,
        //            "reduceOnly" => false,
        //            "timestamp" => "2019-12-11T17:17:33.888Z",
        //            "lastUpdateTimestamp" => "2019-12-11T17:17:33.888Z"
        //        ),
        //        "takerReducedQuantity" => null,
        //        "type" => "EXECUTION"
        //    }
        //
        $timestamp = $this->parse8601($this->safe_string_2($trade, 'time', 'fillTime'));
        $price = $this->safe_string($trade, 'price');
        $amount = $this->safe_string_n($trade, array( 'size', 'amount', 'quantity' ), '0.0');
        $id = $this->safe_string_2($trade, 'uid', 'fill_id');
        if ($id === null) {
            $id = $this->safe_string($trade, 'executionId');
        }
        $order = $this->safe_string($trade, 'order_id');
        $marketId = $this->safe_string($trade, 'symbol');
        $side = $this->safe_string($trade, 'side');
        $type = null;
        $priorEdit = $this->safe_value($trade, 'orderPriorEdit');
        $priorExecution = $this->safe_value($trade, 'orderPriorExecution');
        if ($priorExecution !== null) {
            $order = $this->safe_string($priorExecution, 'orderId');
            $marketId = $this->safe_string($priorExecution, 'symbol');
            $side = $this->safe_string($priorExecution, 'side');
            $type = $this->safe_string($priorExecution, 'type');
        } elseif ($priorEdit !== null) {
            $order = $this->safe_string($priorEdit, 'orderId');
            $marketId = $this->safe_string($priorEdit, 'symbol');
            $side = $this->safe_string($priorEdit, 'type');
            $type = $this->safe_string($priorEdit, 'type');
        }
        if ($type !== null) {
            $type = $this->parse_order_type($type);
        }
        $market = $this->safe_market($marketId, $market);
        $cost = null;
        $linear = $this->safe_bool($market, 'linear');
        if (($amount !== null) && ($price !== null) && ($market !== null)) {
            if ($linear) {
                $cost = Precise::string_mul($amount, $price); // in quote
            } else {
                $cost = Precise::string_div($amount, $price); // in base
            }
            $contractSize = $this->safe_string($market, 'contractSize');
            $cost = Precise::string_mul($cost, $contractSize);
        }
        $takerOrMaker = null;
        $fillType = $this->safe_string($trade, 'fillType');
        if ($fillType !== null) {
            if (mb_strpos($fillType, 'taker') !== false) {
                $takerOrMaker = 'taker';
            } elseif (mb_strpos($fillType, 'maker') !== false) {
                $takerOrMaker = 'maker';
            }
        }
        $isHistoricalExecution = (is_array($trade) && array_key_exists('takerOrder', $trade));
        if ($isHistoricalExecution) {
            $timestamp = $this->safe_integer($trade, 'timestamp');
            $taker = $this->safe_dict($trade, 'takerOrder', array());
            if ($taker !== null) {
                $side = $this->safe_string_lower($taker, 'direction');
                $takerOrMaker = 'taker';
            }
        }
        return $this->safe_trade(array(
            'info' => $trade,
            'id' => $id,
            'symbol' => $this->safe_string($market, 'symbol'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'order' => $order,
            'type' => $type,
            'side' => $side,
            'takerOrMaker' => $takerOrMaker,
            'price' => $price,
            'amount' => $linear ? $amount : null,
            'cost' => $cost,
            'fee' => null,
        ));
    }

    public function create_order_request(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        $market = $this->market($symbol);
        $symbol = $market['symbol'];
        $type = $this->safe_string($params, 'orderType', $type);
        $timeInForce = $this->safe_string($params, 'timeInForce');
        $postOnly = false;
        list($postOnly, $params) = $this->handle_post_only($type === 'market', $type === 'post', $params);
        if ($postOnly) {
            $type = 'post';
        } elseif ($timeInForce === 'ioc') {
            $type = 'ioc';
        } elseif ($type === 'limit') {
            $type = 'lmt';
        } elseif ($type === 'market') {
            $type = 'mkt';
        }
        $request = array(
            'symbol' => $market['id'],
            'side' => $side,
            'size' => $this->amount_to_precision($symbol, $amount),
        );
        $clientOrderId = $this->safe_string_2($params, 'clientOrderId', 'cliOrdId');
        if ($clientOrderId !== null) {
            $request['cliOrdId'] = $clientOrderId;
        }
        $triggerPrice = $this->safe_string_2($params, 'triggerPrice', 'stopPrice');
        $isTriggerOrder = $triggerPrice !== null;
        $stopLossTriggerPrice = $this->safe_string($params, 'stopLossPrice');
        $takeProfitTriggerPrice = $this->safe_string($params, 'takeProfitPrice');
        $isStopLossTriggerOrder = $stopLossTriggerPrice !== null;
        $isTakeProfitTriggerOrder = $takeProfitTriggerPrice !== null;
        $isStopLossOrTakeProfitTrigger = $isStopLossTriggerOrder || $isTakeProfitTriggerOrder;
        $triggerSignal = $this->safe_string($params, 'triggerSignal', 'last');
        $reduceOnly = $this->safe_value($params, 'reduceOnly');
        if ($isStopLossOrTakeProfitTrigger || $isTriggerOrder) {
            $request['triggerSignal'] = $triggerSignal;
        }
        if ($isTriggerOrder) {
            $type = 'stp';
            $request['stopPrice'] = $this->price_to_precision($symbol, $triggerPrice);
        } elseif ($isStopLossOrTakeProfitTrigger) {
            $reduceOnly = true;
            if ($isStopLossTriggerOrder) {
                $type = 'stp';
                $request['stopPrice'] = $this->price_to_precision($symbol, $stopLossTriggerPrice);
            } elseif ($isTakeProfitTriggerOrder) {
                $type = 'take_profit';
                $request['stopPrice'] = $this->price_to_precision($symbol, $takeProfitTriggerPrice);
            }
        }
        if ($reduceOnly) {
            $request['reduceOnly'] = true;
        }
        $request['orderType'] = $type;
        if ($price !== null) {
            $request['limitPrice'] = $this->price_to_precision($symbol, $price);
        }
        $params = $this->omit($params, array( 'clientOrderId', 'timeInForce', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice' ));
        return $this->extend($request, $params);
    }

    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 an order on the exchange
             *
             * @see https://docs.kraken.com/api/docs/futures-api/trading/send-order
             *
             * @param {string} $symbol unified $market $symbol
             * @param {string} $type 'limit' or 'market'
             * @param {string} $side 'buy' or 'sell'
             * @param {float} $amount number of contracts
             * @param {float} [$price] limit order $price
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {bool} [$params->reduceOnly] set if you wish the order to only reduce an existing position, any order which increases an existing position will be rejected, default is false
             * @param {bool} [$params->postOnly] set if you wish to make a postOnly order, default is false
             * @param {string} [$params->clientOrderId] UUID The order identity that is specified from the user, It must be globally unique
             * @param {float} [$params->triggerPrice] the $price that a stop order is triggered at
             * @param {float} [$params->stopLossPrice] the $price that a stop loss order is triggered at
             * @param {float} [$params->takeProfitPrice] the $price that a take profit order is triggered at
             * @param {string} [$params->triggerSignal] for triggerPrice, stopLossPrice and takeProfitPrice orders, the trigger $price $type, 'last', 'mark' or 'index', default is 'last'
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $orderRequest = $this->create_order_request($symbol, $type, $side, $amount, $price, $params);
            $response = Async\await($this->privatePostSendorder ($orderRequest));
            //
            //    {
            //        "result" => "success",
            //        "sendStatus" => {
            //            "order_id" => "salf320-e337-47ac-b345-30sdfsalj",
            //            "status" => "placed",
            //            "receivedTime" => "2022-02-28T19:32:17.122Z",
            //            "orderEvents" => array(
            //                array(
            //                    "order" => array(
            //                        "orderId" => "salf320-e337-47ac-b345-30sdfsalj",
            //                        "cliOrdId" => null,
            //                        "type" => "lmt",
            //                        "symbol" => "pi_xrpusd",
            //                        "side" => "buy",
            //                        "quantity" => 1,
            //                        "filled" => 0,
            //                        "limitPrice" => 0.7,
            //                        "reduceOnly" => false,
            //                        "timestamp" => "2022-02-28T19:32:17.122Z",
            //                        "lastUpdateTimestamp" => "2022-02-28T19:32:17.122Z"
            //                    ),
            //                    "reducedQuantity" => null,
            //                    "type" => "PLACE"
            //                }
            //            )
            //        ),
            //        "serverTime" => "2022-02-28T19:32:17.122Z"
            //    }
            //
            $sendStatus = $this->safe_value($response, 'sendStatus');
            $status = $this->safe_string($sendStatus, 'status');
            $this->verify_order_action_success($status, 'createOrder', array( 'filled' ));
            return $this->parse_order($sendStatus, $market);
        }) ();
    }

    public function create_orders(array $orders, $params = array ()) {
        return Async\async(function () use ($orders, $params) {
            /**
             * create a list of trade $orders
             *
             * @see https://docs.kraken.com/api/docs/futures-api/trading/send-batch-order
             *
             * @param {Array} $orders list of $orders to create, each object should contain the parameters required by createOrder, namely symbol, $type, $side, $amount, $price and $params
             * @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());
            $ordersRequests = array();
            for ($i = 0; $i < count($orders); $i++) {
                $rawOrder = $orders[$i];
                $marketId = $this->safe_string($rawOrder, 'symbol');
                $type = $this->safe_string($rawOrder, 'type');
                $side = $this->safe_string($rawOrder, 'side');
                $amount = $this->safe_value($rawOrder, 'amount');
                $price = $this->safe_value($rawOrder, 'price');
                $orderParams = $this->safe_value($rawOrder, 'params', array());
                $extendedParams = $this->extend($orderParams, $params); // the $request does not accept extra $params since it's a list, so we're extending each order with the common $params
                if (!(is_array($extendedParams) && array_key_exists('order_tag', $extendedParams))) {
                    // order tag is mandatory so we will generate one if not provided
                    $extendedParams['order_tag'] = $this->sum($i, (string) 1); // sequential counter
                }
                $extendedParams['order'] = 'send';
                $orderRequest = $this->create_order_request($marketId, $type, $side, $amount, $price, $extendedParams);
                $ordersRequests[] = $orderRequest;
            }
            $request = array(
                'batchOrder' => $ordersRequests,
            );
            $response = Async\await($this->privatePostBatchorder ($this->extend($request, $params)));
            //
            // {
            //     "result" => "success",
            //     "serverTime" => "2023-10-24T08:40:57.339Z",
            //     "batchStatus" => array(
            //        array(
            //           "status" => "requiredArgumentMissing",
            //           "orderEvents" => array()
            //        ),
            //        {
            //           "status" => "requiredArgumentMissing",
            //           "orderEvents" => array()
            //        }
            //     )
            // }
            //
            $data = $this->safe_list($response, 'batchStatus', array());
            return $this->parse_orders($data);
        }) ();
    }

    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) {
            /**
             *
             * @see https://docs.kraken.com/api/docs/futures-api/trading/edit-$order-spring
             *
             * Edit an open $order on the exchange
             * @param {string} $id $order $id
             * @param {string} $symbol Not used by Krakenfutures
             * @param {string} $type Not used by Krakenfutures
             * @param {string} $side Not used by Krakenfutures
             * @param {float} $amount Order size
             * @param {float} [$price] Price to fill $order at
             * @param {array} [$params] Exchange specific $params
             * @return An ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structure~
             */
            Async\await($this->load_markets());
            $request = array(
                'orderId' => $id,
            );
            if ($amount !== null) {
                $request['size'] = $amount;
            }
            if ($price !== null) {
                $request['limitPrice'] = $price;
            }
            $response = Async\await($this->privatePostEditorder ($this->extend($request, $params)));
            $status = $this->safe_string($response['editStatus'], 'status');
            $this->verify_order_action_success($status, 'editOrder', array( 'filled' ));
            $order = $this->parse_order($response['editStatus']);
            $order['info'] = $response;
            return $order;
        }) ();
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $params) {
            /**
             *
             * @see https://docs.kraken.com/api/docs/futures-api/trading/cancel-$order
             *
             * Cancel an open $order on the exchange
             * @param {string} $id Order $id
             * @param {string} $symbol Not used by Krakenfutures
             * @param {array} [$params] Exchange specific $params
             * @return An ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structure~
             */
            Async\await($this->load_markets());
            $response = Async\await($this->privatePostCancelorder ($this->extend(array( 'order_id' => $id ), $params)));
            $status = $this->safe_string($this->safe_value($response, 'cancelStatus', array()), 'status');
            $this->verify_order_action_success($status, 'cancelOrder');
            $order = array();
            if (is_array($response) && array_key_exists('cancelStatus', $response)) {
                $order = $this->parse_order($response['cancelStatus']);
            }
            return $this->extend(array( 'info' => $response ), $order);
        }) ();
    }

    public function cancel_orders(array $ids, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($ids, $symbol, $params) {
            /**
             * cancel multiple $orders
             *
             * @see https://docs.kraken.com/api/docs/futures-api/trading/send-batch-order
             *
             * @param {string[]} $ids order $ids
             * @param {string} [$symbol] unified market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             *
             * EXCHANGE SPECIFIC PARAMETERS
             * @param {string[]} [$params->clientOrderIds] max length 10 e.g. ["my_id_1","my_id_2"]
             * @return {array} an list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $orders = array();
            $clientOrderIds = $this->safe_value($params, 'clientOrderIds', array());
            $clientOrderIdsLength = count($clientOrderIds);
            if ($clientOrderIdsLength > 0) {
                for ($i = 0; $i < count($clientOrderIds); $i++) {
                    $orders[] = array( 'order' => 'cancel', 'cliOrdId' => $clientOrderIds[$i] );
                }
            } else {
                for ($i = 0; $i < count($ids); $i++) {
                    $orders[] = array( 'order' => 'cancel', 'order_id' => $ids[$i] );
                }
            }
            $request = array(
                'batchOrder' => $orders,
            );
            $response = Async\await($this->privatePostBatchorder ($this->extend($request, $params)));
            // {
            //     "result" => "success",
            //     "serverTime" => "2023-10-23T16:36:51.327Z",
            //     "batchStatus" => array(
            //       {
            //         "status" => "cancelled",
            //         "order_id" => "101c2327-f12e-45f2-8445-7502b87afc0b",
            //         "orderEvents" => array(
            //           {
            //             "uid" => "101c2327-f12e-45f2-8445-7502b87afc0b",
            //             "order" => array(
            //               "orderId" => "101c2327-f12e-45f2-8445-7502b87afc0b",
            //               "cliOrdId" => null,
            //               "type" => "lmt",
            //               "symbol" => "PF_LTCUSD",
            //               "side" => "buy",
            //               "quantity" => "0.10000000000",
            //               "filled" => "0E-11",
            //               "limitPrice" => "50.00000000000",
            //               "reduceOnly" => false,
            //               "timestamp" => "2023-10-20T10:29:13.005Z",
            //               "lastUpdateTimestamp" => "2023-10-20T10:29:13.005Z"
            //             ),
            //             "type" => "CANCEL"
            //           }
            //         )
            //       }
            //     )
            // }
            $batchStatus = $this->safe_list($response, 'batchStatus', array());
            return $this->parse_orders($batchStatus);
        }) ();
    }

    public function cancel_all_orders(?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             *
             * @see https://docs.kraken.com/api/docs/futures-api/trading/cancel-all-$orders
             *
             * Cancels all $orders on the exchange, including trigger $orders
             * @param {str} $symbol Unified market $symbol
             * @param {dict} [$params] Exchange specific $params
             * @return Response from exchange api
             */
            $request = array();
            if ($symbol !== null) {
                $request['symbol'] = $this->market_id($symbol);
            }
            $response = Async\await($this->privatePostCancelallorders ($this->extend($request, $params)));
            //
            //    {
            //        result => 'success',
            //        $cancelStatus => {
            //          receivedTime => '2024-06-06T01:12:44.814Z',
            //          cancelOnly => 'PF_XRPUSD',
            //          status => 'cancelled',
            //          cancelledOrders => array( array( order_id => '272fd0ac-45c0-4003-b84d-d39b9e86bd36' ) ),
            //          $orderEvents => array(
            //            array(
            //              uid => '272fd0ac-45c0-4003-b84d-d39b9e86bd36',
            //              $order => array(
            //                orderId => '272fd0ac-45c0-4003-b84d-d39b9e86bd36',
            //                cliOrdId => null,
            //                type => 'lmt',
            //                $symbol => 'PF_XRPUSD',
            //                side => 'buy',
            //                quantity => '10',
            //                filled => '0',
            //                limitPrice => '0.4',
            //                reduceOnly => false,
            //                timestamp => '2024-06-06T01:11:16.045Z',
            //                lastUpdateTimestamp => '2024-06-06T01:11:16.045Z'
            //              ),
            //              type => 'CANCEL'
            //            }
            //          )
            //        ),
            //        serverTime => '2024-06-06T01:12:44.814Z'
            //    }
            //
            $cancelStatus = $this->safe_dict($response, 'cancelStatus');
            $orderEvents = $this->safe_list($cancelStatus, 'orderEvents', array());
            $orders = array();
            for ($i = 0; $i < count($orderEvents); $i++) {
                $orderEvent = $this->safe_dict($orderEvents, 0);
                $order = $this->safe_dict($orderEvent, 'order', array());
                $orders[] = $order;
            }
            return $this->parse_orders($orders);
        }) ();
    }

    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://docs.kraken.com/api/docs/futures-api/trading/cancel-all-orders-after
             *
             * @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->privatePostCancelallordersafter ($this->extend($request, $params)));
            //
            //     {
            //         "result" => "success",
            //         "serverTime" => "2018-06-19T16:51:23.839Z",
            //         "status" => {
            //             "currentTime" => "2018-06-19T16:51:23.839Z",
            //             "triggerTime" => "0"
            //         }
            //     }
            //
            return $response;
        }) ();
    }

    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) {
            /**
             *
             * @see https://docs.kraken.com/api/docs/futures-api/trading/get-open-$orders
             *
             * Gets all open $orders, including trigger $orders, for an account from the exchange api
             * @param {string} $symbol Unified $market $symbol
             * @param {int} [$since] Timestamp (ms) of earliest order. (Not used by kraken api but filtered internally by CCXT)
             * @param {int} [$limit] How many $orders to return. (Not used by kraken api but filtered internally by CCXT)
             * @param {array} [$params] Exchange specific parameters
             * @return An array of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
            }
            $response = Async\await($this->privateGetOpenorders ($params));
            $orders = $this->safe_list($response, 'openOrders', array());
            return $this->parse_orders($orders, $market, $since, $limit);
        }) ();
    }

    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) {
            /**
             *
             * @see https://docs.futures.kraken.com/#http-api-history-account-history-get-$order-events
             *
             * Gets all closed orders, including trigger orders, for an account from the exchange api
             * @param {string} $symbol Unified $market $symbol
             * @param {int} [$since] Timestamp (ms) of earliest $order->
             * @param {int} [$limit] How many orders to return.
             * @param {array} [$params] Exchange specific parameters
             * @return An array of ~@link https://docs.ccxt.com/#/?id=$order-structure $order structures~
             */
            Async\await($this->load_markets());
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
            }
            $request = array();
            if ($limit !== null) {
                $request['count'] = $limit;
            }
            if ($since !== null) {
                $request['from'] = $since;
            }
            $response = Async\await($this->historyGetOrders ($this->extend($request, $params)));
            $allOrders = $this->safe_list($response, 'elements', array());
            $closedOrders = array();
            for ($i = 0; $i < count($allOrders); $i++) {
                $order = $allOrders[$i];
                $event = $this->safe_dict($order, 'event', array());
                $orderPlaced = $this->safe_dict($event, 'OrderPlaced');
                if ($orderPlaced !== null) {
                    $innerOrder = $this->safe_dict($orderPlaced, 'order', array());
                    $filled = $this->safe_string($innerOrder, 'filled');
                    if ($filled !== '0') {
                        $innerOrder['status'] = 'closed'; // status not available in the $response
                        $closedOrders[] = $innerOrder;
                    }
                }
            }
            return $this->parse_orders($closedOrders, $market, $since, $limit);
        }) ();
    }

    public function fetch_canceled_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             *
             * @see https://docs.kraken.com/api/docs/futures-api/history/get-$order-events
             *
             * Gets all canceled orders, including trigger orders, for an account from the exchange api
             * @param {string} $symbol Unified $market $symbol
             * @param {int} [$since] Timestamp (ms) of earliest $order->
             * @param {int} [$limit] How many orders to return.
             * @param {array} [$params] Exchange specific parameters
             * @return An array of ~@link https://docs.ccxt.com/#/?id=$order-structure $order structures~
             */
            Async\await($this->load_markets());
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
            }
            $request = array();
            if ($limit !== null) {
                $request['count'] = $limit;
            }
            if ($since !== null) {
                $request['from'] = $since;
            }
            $response = Async\await($this->historyGetOrders ($this->extend($request, $params)));
            $allOrders = $this->safe_list($response, 'elements', array());
            $canceledAndRejected = array();
            for ($i = 0; $i < count($allOrders); $i++) {
                $order = $allOrders[$i];
                $event = $this->safe_dict($order, 'event', array());
                $orderPlaced = $this->safe_dict($event, 'OrderPlaced');
                if ($orderPlaced !== null) {
                    $innerOrder = $this->safe_dict($orderPlaced, 'order', array());
                    $filled = $this->safe_string($innerOrder, 'filled');
                    if ($filled === '0') {
                        $innerOrder['status'] = 'canceled'; // status not available in the $response
                        $canceledAndRejected[] = $innerOrder;
                    }
                }
                $orderCanceled = $this->safe_dict($event, 'OrderCancelled');
                if ($orderCanceled !== null) {
                    $innerOrder = $this->safe_dict($orderCanceled, 'order', array());
                    $innerOrder['status'] = 'canceled'; // status not available in the $response
                    $canceledAndRejected[] = $innerOrder;
                }
                $orderRejected = $this->safe_dict($event, 'OrderRejected');
                if ($orderRejected !== null) {
                    $innerOrder = $this->safe_dict($orderRejected, 'order', array());
                    $innerOrder['status'] = 'rejected'; // status not available in the $response
                    $canceledAndRejected[] = $innerOrder;
                }
            }
            return $this->parse_orders($canceledAndRejected, $market, $since, $limit);
        }) ();
    }

    public function parse_order_type($orderType) {
        $typesMap = array(
            'lmt' => 'limit',
            'mkt' => 'market',
            'post' => 'limit',
            'ioc' => 'market',
        );
        return $this->safe_string($typesMap, $orderType, $orderType);
    }

    public function verify_order_action_success($status, $method, $omit = []) {
        $errors = array(
            'invalidOrderType' => '\\ccxt\\InvalidOrder',
            'invalidSide' => '\\ccxt\\InvalidOrder',
            'invalidSize' => '\\ccxt\\InvalidOrder',
            'invalidPrice' => '\\ccxt\\InvalidOrder',
            'insufficientAvailableFunds' => '\\ccxt\\InsufficientFunds',
            'selfFill' => '\\ccxt\\ExchangeError',
            'tooManySmallOrders' => '\\ccxt\\ExchangeError',
            'maxPositionViolation' => '\\ccxt\\BadRequest',
            'marketSuspended' => '\\ccxt\\ExchangeNotAvailable',
            'marketInactive' => '\\ccxt\\ExchangeNotAvailable',
            'clientOrderIdAlreadyExist' => '\\ccxt\\DuplicateOrderId',
            'clientOrderIdTooLong' => '\\ccxt\\BadRequest',
            'outsidePriceCollar' => '\\ccxt\\InvalidOrder',
            'postWouldExecute' => '\\ccxt\\OrderImmediatelyFillable',  // the unplaced order could actually be parsed (with $status = "rejected"), but there is this specific error for this
            'iocWouldNotExecute' => '\\ccxt\\OrderNotFillable', // -||-
            'wouldNotReducePosition' => '\\ccxt\\ExchangeError',
            'orderForEditNotFound' => '\\ccxt\\OrderNotFound',
            'orderForEditNotAStop' => '\\ccxt\\InvalidOrder',
            'filled' => '\\ccxt\\OrderNotFound',
            'notFound' => '\\ccxt\\OrderNotFound',
        );
        if ((is_array($errors) && array_key_exists($status, $errors)) && !$this->in_array($status, $omit)) {
            throw new $errors[$status]($this->id . ' => ' . $method . ' failed due to ' . $status);
        }
    }

    public function parse_order_status(?string $status) {
        $statuses = array(
            'placed' => 'open', // the order was placed successfully
            'cancelled' => 'canceled', // the order was cancelled successfully
            'invalidOrderType' => 'rejected', // the order was not placed because orderType is invalid
            'invalidSide' => 'rejected', // the order was not placed because side is invalid
            'invalidSize' => 'rejected', // the order was not placed because size is invalid
            'invalidPrice' => 'rejected', // the order was not placed because limitPrice and/or stopPrice are invalid
            'insufficientAvailableFunds' => 'rejected', // the order was not placed because available funds are insufficient
            'selfFill' => 'rejected', // the order was not placed because it would be filled against an existing order belonging to the same account
            'tooManySmallOrders' => 'rejected', // the order was not placed because the number of small open orders would exceed the permissible limit
            'maxPositionViolation' => 'rejected', // Order would cause you to exceed your maximum property_exists($this, position) contract.
            'marketSuspended' => 'rejected', // the order was not placed because the market is suspended
            'marketInactive' => 'rejected', // the order was not placed because the market is inactive
            'clientOrderIdAlreadyExist' => 'rejected', // the specified client id already exist
            'clientOrderIdTooLong' => 'rejected', // the client id is longer than the permissible limit
            'outsidePriceCollar' => 'rejected', // the limit order crosses the spread but is an order of magnitude away from the mark price - fat finger control
            // Should the next two be 'expired' ?
            'postWouldExecute' => 'rejected', // the post-only order would be filled upon placement, thus is cancelled
            'iocWouldNotExecute' => 'rejected', // the immediate-or-cancel order would not execute.
            'wouldNotReducePosition' => 'rejected', // the reduce only order would not reduce position.
            'edited' => 'open', // the order was edited successfully
            'orderForEditNotFound' => 'rejected', // the requested order for edit has not been found
            'orderForEditNotAStop' => 'rejected', // the supplied stopPrice cannot be applied because order is not a stop order
            'filled' => 'closed', // the order was found completely filled and could not be cancelled
            'notFound' => 'rejected', // the order was not found, either because it had already been cancelled or it never existed
            'untouched' => 'open', // the entire size of the order is unfilled
            'partiallyFilled' => 'open', // the size of the order is partially but not entirely filled
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        // LIMIT
        //
        //    {
        //        "order_id" => "179f9af8-e45e-469d-b3e9-2fd4675cb7d0",
        //        "status" => "placed",
        //        "receivedTime" => "2019-09-05T16:33:50.734Z",
        //        "orderEvents" => array(
        //            {
        //                "uid" => "614a5298-0071-450f-83c6-0617ce8c6bc4",
        //                "order" => array(
        //                    "orderId" => "179f9af8-e45e-469d-b3e9-2fd4675cb7d0",
        //                    "cliOrdId" => null,
        //                    "type" => "lmt",
        //                    "symbol" => "pi_xbtusd",
        //                    "side" => "buy",
        //                    "quantity" => 10000,
        //                    "filled" => 0,
        //                    "limitPrice" => 9400,
        //                    "reduceOnly" => false,
        //                    "timestamp" => "2019-09-05T16:33:50.734Z",
        //                    "lastUpdateTimestamp" => "2019-09-05T16:33:50.734Z"
        //                ),
        //                "reducedQuantity" => null,
        //                "reason" => "WOULD_NOT_REDUCE_POSITION", // REJECTED
        //                "type" => "PLACE"
        //            }
        //        )
        //    }
        //
        // CONDITIONAL
        //
        //    {
        //        "order_id" => "1abfd3c6-af93-4b30-91cc-e4a93797f3f5",
        //        "status" => "placed",
        //        "receivedTime" => "2019-12-05T10:20:50.701Z",
        //        "orderEvents" => array(
        //            {
        //                "orderTrigger" => array(
        //                    "uid" => "1abfd3c6-af93-4b30-91cc-e4a93797f3f5",
        //                    "clientId":null,
        //                    "type" => "lmt",                                // "ioc" if stop $market
        //                    "symbol" => "pi_xbtusd",
        //                    "side" => "buy",
        //                    "quantity":10,
        //                    "limitPrice":15000,
        //                    "triggerPrice":9500,
        //                    "triggerSide" => "trigger_below",
        //                    "triggerSignal" => "mark_price",
        //                    "reduceOnly":false,
        //                    "timestamp" => "2019-12-05T10:20:50.701Z",
        //                    "lastUpdateTimestamp" => "2019-12-05T10:20:50.701Z"
        //                ),
        //                "type" => "PLACE"
        //            }
        //        )
        //    }
        //
        // EXECUTION
        //
        //    {
        //        "order_id" => "61ca5732-3478-42fe-8362-abbfd9465294",
        //        "status" => "placed",
        //        "receivedTime" => "2019-12-11T17:17:33.888Z",
        //        "orderEvents" => array(
        //            {
        //                "executionId" => "e1ec9f63-2338-4c44-b40a-43486c6732d7",
        //                "price" => 7244.5,
        //                "amount" => 10,
        //                "orderPriorEdit" => null,
        //                "orderPriorExecution" => array(
        //                    "orderId" => "61ca5732-3478-42fe-8362-abbfd9465294",
        //                    "cliOrdId" => null,
        //                    "type" => "lmt",
        //                    "symbol" => "pi_xbtusd",
        //                    "side" => "buy",
        //                    "quantity" => 10,
        //                    "filled" => 0,
        //                    "limitPrice" => 7500,
        //                    "reduceOnly" => false,
        //                    "timestamp" => "2019-12-11T17:17:33.888Z",
        //                    "lastUpdateTimestamp" => "2019-12-11T17:17:33.888Z"
        //                ),
        //                "takerReducedQuantity" => null,
        //                "type" => "EXECUTION"
        //            }
        //        )
        //    }
        //
        // EDIT ORDER
        //
        //    {
        //        "status" => "edited",
        //        "orderId" => "022774bc-2c4a-4f26-9317-436c8d85746d",
        //        "receivedTime" => "2019-09-05T16:47:47.521Z",
        //        "orderEvents" => array(
        //            {
        //                "old" => array(
        //                    "orderId" => "022774bc-2c4a-4f26-9317-436c8d85746d",
        //                    "cliOrdId":null,
        //                    "type" => "lmt",
        //                    "symbol" => "pi_xbtusd",
        //                    "side" => "buy",
        //                    "quantity":1000,
        //                    "filled":0,
        //                    "limitPrice":9400.0,
        //                    "reduceOnly":false,
        //                    "timestamp" => "2019-09-05T16:41:35.173Z",
        //                    "lastUpdateTimestamp" => "2019-09-05T16:41:35.173Z"
        //                ),
        //                "new" => array(
        //                    "orderId" => "022774bc-2c4a-4f26-9317-436c8d85746d",
        //                    "cliOrdId" => null,
        //                    "type" => "lmt",
        //                    "symbol" => "pi_xbtusd",
        //                    "side" => "buy",
        //                    "quantity" => 1501,
        //                    "filled" => 0,
        //                    "limitPrice" => 7200,
        //                    "reduceOnly" => false,
        //                    "timestamp" => "2019-09-05T16:41:35.173Z",
        //                    "lastUpdateTimestamp" => "2019-09-05T16:47:47.519Z"
        //                ),
        //                "reducedQuantity" => null,
        //                "type" => "EDIT"
        //            }
        //        )
        //    }
        //
        // CANCEL ORDER
        //
        //    {
        //        "status" => "cancelled",
        //        "orderEvents" => array(
        //            {
        //                "uid" => "85c40002-3f20-4e87-9302-262626c3531b",
        //                "order" => array(
        //                    "orderId" => "85c40002-3f20-4e87-9302-262626c3531b",
        //                    "cliOrdId" => null,
        //                    "type" => "lmt",
        //                    "symbol" => "pi_xbtusd",
        //                    "side" => "buy",
        //                    "quantity" => 1000,
        //                    "filled" => 0,
        //                    "limitPrice" => 10144,
        //                    "stopPrice" => null,
        //                    "reduceOnly" => false,
        //                    "timestamp" => "2019-08-01T15:26:27.790Z"
        //                ),
        //                "type" => "CANCEL"
        //            }
        //        )
        //    }
        //
        // cancelAllOrders
        //
        //    {
        //        "orderId" => "85c40002-3f20-4e87-9302-262626c3531b",
        //        "cliOrdId" => null,
        //        "type" => "lmt",
        //        "symbol" => "pi_xbtusd",
        //        "side" => "buy",
        //        "quantity" => 1000,
        //        "filled" => 0,
        //        "limitPrice" => 10144,
        //        "stopPrice" => null,
        //        "reduceOnly" => false,
        //        "timestamp" => "2019-08-01T15:26:27.790Z"
        //    }
        //
        // FETCH OPEN ORDERS
        //
        //    {
        //        "order_id" => "59302619-41d2-4f0b-941f-7e7914760ad3",
        //        "symbol" => "pi_xbtusd",
        //        "side" => "sell",
        //        "orderType" => "lmt",
        //        "limitPrice" => 10640,
        //        "unfilledSize" => 304,
        //        "receivedTime" => "2019-09-05T17:01:17.410Z",
        //        "status" => "untouched",
        //        "filledSize" => 0,
        //        "reduceOnly" => true,
        //        "lastUpdateTime" => "2019-09-05T17:01:17.410Z"
        //    }
        //
        // createOrders error
        //    {
        //       "status" => "requiredArgumentMissing",
        //       "orderEvents" => array()
        //    }
        // closed orders
        //    {
        //        uid => '2f00cd63-e61d-44f8-8569-adabde885941',
        //        $timestamp => '1707258274849',
        //        event => {
        //          OrderPlaced => {
        //            $order => array(
        //              uid => '85805e01-9eed-4395-8360-ed1a228237c9',
        //              accountUid => '406142dd-7c5c-4a8b-acbc-5f16eca30009',
        //              tradeable => 'PF_LTCUSD',
        //              direction => 'Buy',
        //              quantity => '0',
        //              $filled => '0.1',
        //              $timestamp => '1707258274849',
        //              limitPrice => '69.2200000000',
        //              orderType => 'IoC',
        //              clientId => '',
        //              reduceOnly => false,
        //              $lastUpdateTimestamp => '1707258274849'
        //            ),
        //            reason => 'new_user_order',
        //            reducedQuantity => '',
        //            algoId => ''
        //          }
        //        }
        //    }
        //
        $orderEvents = $this->safe_value($order, 'orderEvents', array());
        $errorStatus = $this->safe_string($order, 'status');
        $orderEventsLength = count($orderEvents);
        if ((is_array($order) && array_key_exists('orderEvents', $order)) && ($errorStatus !== null) && ($orderEventsLength === 0)) {
            // creteOrders error response
            return $this->safe_order(array( 'info' => $order, 'status' => 'rejected' ));
        }
        $details = null;
        $isPrior = false;
        $fixed = false;
        $statusId = null;
        $price = null;
        $trades = array();
        if ($orderEventsLength) {
            $executions = array();
            for ($i = 0; $i < count($orderEvents); $i++) {
                $item = $orderEvents[$i];
                if ($this->safe_string($item, 'type') === 'EXECUTION') {
                    $executions[] = $item;
                }
                // Final $order (after placement / editing / execution / canceling)
                $orderTrigger = $this->safe_value($item, 'orderTrigger');
                if ($details === null) {
                    $details = $this->safe_value_2($item, 'new', 'order', $orderTrigger);
                    if ($details !== null) {
                        $isPrior = false;
                        $fixed = true;
                    } elseif (!$fixed) {
                        $orderPriorExecution = $this->safe_value($item, 'orderPriorExecution');
                        $details = $this->safe_value_2($item, 'orderPriorExecution', 'orderPriorEdit');
                        $price = $this->safe_string($orderPriorExecution, 'limitPrice');
                        if ($details !== null) {
                            $isPrior = true;
                        }
                    }
                }
            }
            $trades = $this->parse_trades($executions);
            $statusId = $this->safe_string($order, 'status');
        }
        if ($details === null) {
            $details = $order;
        }
        if ($statusId === null) {
            $statusId = $this->safe_string($details, 'status');
        }
        // This may be incorrectly marked as "open" if only execution report is given,
        // but will be $fixed below
        $status = $this->parse_order_status($statusId);
        $isClosed = $this->in_array($status, array( 'canceled', 'rejected', 'closed' ));
        $marketId = $this->safe_string($details, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $timestamp = $this->parse8601($this->safe_string_2($details, 'timestamp', 'receivedTime'));
        $lastUpdateTimestamp = $this->parse8601($this->safe_string($details, 'lastUpdateTime'));
        if ($price === null) {
            $price = $this->safe_string($details, 'limitPrice');
        }
        $amount = $this->safe_string($details, 'quantity');
        $filled = $this->safe_string_2($details, 'filledSize', 'filled', '0.0');
        $remaining = $this->safe_string($details, 'unfilledSize');
        $average = null;
        $filled2 = '0.0';
        $tradesLength = count($trades);
        if ($tradesLength > 0) {
            $vwapSum = '0.0';
            for ($i = 0; $i < count($trades); $i++) {
                $trade = $trades[$i];
                $tradeAmount = $this->safe_string($trade, 'amount');
                $tradePrice = $this->safe_string($trade, 'price');
                $filled2 = Precise::string_add($filled2, $tradeAmount);
                $vwapSum = Precise::string_add($vwapSum, Precise::string_mul($tradeAmount, $tradePrice));
            }
            $average = Precise::string_div($vwapSum, $filled2);
            if (($amount !== null) && (!$isClosed) && $isPrior && Precise::string_ge($filled2, $amount)) {
                $status = 'closed';
                $isClosed = true;
            }
            if ($isPrior) {
                $filled = Precise::string_add($filled, $filled2);
            } else {
                $filled = Precise::string_max($filled, $filled2);
            }
        }
        if ($remaining === null) {
            if ($isPrior) {
                if ($amount !== null) {
                    // $remaining $amount before execution minus executed $amount
                    $remaining = Precise::string_sub($amount, $filled2);
                }
            } else {
                $remaining = $amount;
            }
        }
        // if fetchOpenOrders are parsed
        if (($amount === null) && (!$isPrior) && ($remaining !== null)) {
            $amount = Precise::string_add($filled, $remaining);
        }
        $cost = null;
        if (($filled !== null) && ($market !== null)) {
            $whichPrice = ($average !== null) ? $average : $price;
            if ($whichPrice !== null) {
                if ($market['linear']) {
                    $cost = Precise::string_mul($filled, $whichPrice); // in quote
                } else {
                    $cost = Precise::string_div($filled, $whichPrice); // in base
                }
            }
        }
        $id = $this->safe_string_2($order, 'order_id', 'orderId');
        if ($id === null) {
            $id = $this->safe_string_2($details, 'orderId', 'uid');
        }
        $type = $this->safe_string_lower_2($details, 'type', 'orderType');
        $timeInForce = 'gtc';
        if ($type === 'ioc' || $this->parse_order_type($type) === 'market') {
            $timeInForce = 'ioc';
        }
        return $this->safe_order(array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => $this->safe_string_n($details, array( 'clientOrderId', 'clientId', 'cliOrdId' )),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'lastUpdateTimestamp' => $lastUpdateTimestamp,
            'symbol' => $this->safe_string($market, 'symbol'),
            'type' => $this->parse_order_type($type),
            'timeInForce' => $timeInForce,
            'postOnly' => $type === 'post',
            'reduceOnly' => $this->safe_bool_2($details, 'reduceOnly', 'reduce_only'),
            'side' => $this->safe_string($details, 'side'),
            'price' => $price,
            'triggerPrice' => $this->safe_string($details, 'triggerPrice'),
            'amount' => $amount,
            'cost' => $cost,
            'average' => $average,
            'filled' => $filled,
            'remaining' => $remaining,
            'status' => $status,
            'fee' => null,
            'fees' => null,
            'trades' => $trades,
        ));
    }

    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://docs.kraken.com/api/docs/futures-api/trading/get-fills
             *
             * @param {string} $symbol unified $market $symbol
             * @param {int} [$since] *not used by the  api* 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 {int} [$params->until] the latest time in ms to fetch entries for
             * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
             */
            Async\await($this->load_markets());
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
            }
            // todo => lastFillTime => $this->iso8601(end)
            $response = Async\await($this->privateGetFills ($params));
            //
            //    {
            //        "result" => "success",
            //        "serverTime" => "2016-02-25T09:45:53.818Z",
            //        "fills" => array(
            //            array(
            //                "fillTime" => "2016-02-25T09:47:01.000Z",
            //                "order_id" => "c18f0c17-9971-40e6-8e5b-10df05d422f0",
            //                "fill_id" => "522d4e08-96e7-4b44-9694-bfaea8fe215e",
            //                "cliOrdId" => "d427f920-ec55-4c18-ba95-5fe241513b30", // EXTRA
            //                "symbol" => "fi_xbtusd_180615",
            //                "side" => "buy",
            //                "size" => 2000,
            //                "price" => 4255,
            //                "fillType" => "maker"
            //            ),
            //            ...
            //        )
            //    }
            //
            return $this->parse_trades($response['fills'], $market, $since, $limit);
        }) ();
    }

    public function fetch_balance($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             *
             * @see https://docs.kraken.com/api/docs/futures-api/trading/get-$accounts
             *
             * Fetch the $balance for a sub-$account, all sub-$account balances are inside 'info' in the $response
             * @param {array} [$params] Exchange specific parameters
             * @param {string} [$params->type] The sub-$account $type to query the $balance of, possible values include 'flex', 'cash'/'main'/'funding', or a market $symbol * defaults to 'flex' *
             * @param {string} [$params->symbol] A unified market $symbol, when assigned the $balance for a trading market that matches the $symbol is returned
             * @return A ~@link https://docs.ccxt.com/#/?id=$balance-structure $balance structure~
             */
            Async\await($this->load_markets());
            $type = $this->safe_string_2($params, 'type', 'account');
            $symbol = $this->safe_string($params, 'symbol');
            $params = $this->omit($params, array( 'type', 'account', 'symbol' ));
            $response = Async\await($this->privateGetAccounts ($params));
            //
            //    {
            //        "result" => "success",
            //        "accounts" => {
            //            "fi_xbtusd" => array(
            //                "auxiliary" => array( usd => "0", pv => '0.0', pnl => '0.0', af => '0.0', funding => "0.0" ),
            //                "marginRequirements" => array( im => '0.0', mm => '0.0', lt => '0.0', tt => "0.0" ),
            //                "triggerEstimates" => array( im => '0', mm => '0', lt => "0", tt => "0" ),
            //                "balances" => array( xbt => "0.0" ),
            //                "currency" => "xbt",
            //                "type" => "marginAccount"
            //            ),
            //            "cash" => array(
            //                "balances" => array(
            //                    "eur" => "0.0",
            //                    "gbp" => "0.0",
            //                    "bch" => "0.0",
            //                    "xrp" => "2.20188538338",
            //                    "usd" => "0.0",
            //                    "eth" => "0.0",
            //                    "usdt" => "0.0",
            //                    "ltc" => "0.0",
            //                    "usdc" => "0.0",
            //                    "xbt" => "0.0"
            //                ),
            //                "type" => "cashAccount"
            //            ),
            //            "fv_xrpxbt" => array(
            //                "auxiliary" => array( usd => "0", pv => '0.0', pnl => '0.0', af => '0.0', funding => "0.0" ),
            //                "marginRequirements" => array( im => '0.0', mm => '0.0', lt => '0.0', tt => "0.0" ),
            //                "triggerEstimates" => array( im => '0', mm => '0', lt => "0", tt => "0" ),
            //                "balances" => array( xbt => "0.0" ),
            //                "currency" => "xbt",
            //                "type" => "marginAccount"
            //            ),
            //            "fi_xrpusd" => array(
            //                "auxiliary" => array( usd => "0", pv => '11.0', pnl => '0.0', af => '11.0', funding => "0.0" ),
            //                "marginRequirements" => array( im => '0.0', mm => '0.0', lt => '0.0', tt => "0.0" ),
            //                "triggerEstimates" => array( im => '0', mm => '0', lt => "0", tt => "0" ),
            //                "balances" => array( xrp => "11.0" ),
            //                "currency" => "xrp",
            //                "type" => "marginAccount"
            //            ),
            //            "fi_ethusd" => array(
            //                "auxiliary" => array( usd => "0", pv => '0.0', pnl => '0.0', af => '0.0', funding => "0.0" ),
            //                "marginRequirements" => array( im => '0.0', mm => '0.0', lt => '0.0', tt => "0.0" ),
            //                "triggerEstimates" => array( im => '0', mm => '0', lt => "0", tt => "0" ),
            //                "balances" => array( eth => "0.0" ),
            //                "currency" => "eth",
            //                "type" => "marginAccount"
            //            ),
            //            "fi_ltcusd" => array(
            //                "auxiliary" => array( usd => "0", pv => '0.0', pnl => '0.0', af => '0.0', funding => "0.0" ),
            //                "marginRequirements" => array( im => '0.0', mm => '0.0', lt => '0.0', tt => "0.0" ),
            //                "triggerEstimates" => array( im => '0', mm => '0', lt => "0", tt => "0" ),
            //                "balances" => array( ltc => "0.0" ),
            //                "currency" => "ltc",
            //                "type" => "marginAccount"
            //            ),
            //            "fi_bchusd" => array(
            //                "auxiliary" => array( usd => "0", pv => '0.0', pnl => '0.0', af => '0.0', funding => "0.0" ),
            //                "marginRequirements" => array( im => '0.0', mm => '0.0', lt => '0.0', tt => "0.0" ),
            //                "triggerEstimates" => array( im => '0', mm => '0', lt => "0", tt => "0" ),
            //                "balances" => array( bch => "0.0" ),
            //                "currency" => "bch",
            //                "type" => "marginAccount"
            //            ),
            //            "flex" => array(
            //                "currencies" => array(),
            //                "initialMargin" => "0.0",
            //                "initialMarginWithOrders" => "0.0",
            //                "maintenanceMargin" => "0.0",
            //                "balanceValue" => "0.0",
            //                "portfolioValue" => "0.0",
            //                "collateralValue" => "0.0",
            //                "pnl" => "0.0",
            //                "unrealizedFunding" => "0.0",
            //                "totalUnrealized" => "0.0",
            //                "totalUnrealizedAsMargin" => "0.0",
            //                "availableMargin" => "0.0",
            //                "marginEquity" => "0.0",
            //                "type" => "multiCollateralMarginAccount"
            //            }
            //        ),
            //        "serverTime" => "2022-04-12T07:48:07.475Z"
            //    }
            //
            $datetime = $this->safe_string($response, 'serverTime');
            if ($type === 'marginAccount' || $type === 'margin') {
                if ($symbol === null) {
                    throw new ArgumentsRequired($this->id . ' fetchBalance requires $symbol argument for margin accounts');
                }
                $type = $symbol;
            }
            if ($type === null) {
                $type = ($symbol === null) ? 'flex' : $symbol;
            }
            $accountName = $this->parse_account($type);
            $accounts = $this->safe_value($response, 'accounts');
            $account = $this->safe_value($accounts, $accountName);
            if ($account === null) {
                $type = ($type === null) ? '' : $type;
                $symbol = ($symbol === null) ? '' : $symbol;
                throw new BadRequest($this->id . ' fetchBalance has no $account for ' . $type);
            }
            $balance = $this->parse_balance($account);
            $balance['info'] = $response;
            $balance['timestamp'] = $this->parse8601($datetime);
            $balance['datetime'] = $datetime;
            return $balance;
        }) ();
    }

    public function parse_balance($response): array {
        //
        // cashAccount
        //
        //    {
        //        "balances" => array(
        //            "eur" => "0.0",
        //            "gbp" => "0.0",
        //            "bch" => "0.0",
        //            "xrp" => "2.20188538338",
        //            "usd" => "0.0",
        //            "eth" => "0.0",
        //            "usdt" => "0.0",
        //            "ltc" => "0.0",
        //            "usdc" => "0.0",
        //            "xbt" => "0.0"
        //        ),
        //        "type" => "cashAccount"
        //    }
        //
        // marginAccount e,g, fi_xrpusd
        //
        //    {
        //        "auxiliary" => array(
        //            "usd" => "0",
        //            "pv" => "11.0",
        //            "pnl" => "0.0",
        //            "af" => "11.0",
        //            "funding" => "0.0"
        //        ),
        //        "marginRequirements" => array( im => '0.0', mm => '0.0', lt => '0.0', tt => "0.0" ),
        //        "triggerEstimates" => array( im => '0', mm => '0', lt => "0", tt => "0" ),
        //        "balances" => array( xrp => "11.0" ),
        //        "currency" => "xrp",
        //        "type" => "marginAccount"
        //    }
        //
        // flex/multiCollateralMarginAccount
        //
        //    {
        //       "currencies" => {
        //            "USDT" => array(
        //                "quantity" => "1",
        //                "value" => "1.0001",
        //                "collateral" => "0.9477197625",
        //                "available" => "1.0"
        //             }
        //       ),
        //       "initialMargin" => "0.0",
        //       "initialMarginWithOrders" => "0.0",
        //       "maintenanceMargin" => "0.0",
        //       "balanceValue" => "1.0",
        //       "portfolioValue" => "1.0",
        //       "collateralValue" => "0.95",
        //       "pnl" => "0.0",
        //       "unrealizedFunding" => "0.0",
        //       "totalUnrealized" => "0.0",
        //       "totalUnrealizedAsMargin" => "0.0",
        //       "availableMargin" => "0.95",
        //       "marginEquity" => "0.95",
        //       "type" => "multiCollateralMarginAccount"
        //    }
        //
        $accountType = $this->safe_string_2($response, 'accountType', 'type');
        $isFlex = ($accountType === 'multiCollateralMarginAccount');
        $isCash = ($accountType === 'cashAccount');
        $balances = $this->safe_value_2($response, 'balances', 'currencies', array());
        $result = array();
        $currencyIds = is_array($balances) ? array_keys($balances) : array();
        for ($i = 0; $i < count($currencyIds); $i++) {
            $currencyId = $currencyIds[$i];
            $balance = $balances[$currencyId];
            $code = $this->safe_currency_code($currencyId);
            $splitCode = explode('_', $code);
            $codeLength = count($splitCode);
            if ($codeLength > 1) {
                continue;   // Removes contract codes like PI_XRPUSD
            }
            $account = $this->account();
            if ($isFlex) {
                $account['total'] = $this->safe_string($balance, 'quantity');
                $account['free'] = $this->safe_string($balance, 'available');
            } elseif ($isCash) {
                $account['used'] = '0.0';
                $account['total'] = $balance;
            } else {
                $auxiliary = $this->safe_value($response, 'auxiliary');
                $account['free'] = $this->safe_string($auxiliary, 'af');
                $account['total'] = $this->safe_string($auxiliary, 'pv');
            }
            $result[$code] = $account;
        }
        return $this->safe_balance($result);
    }

    public function fetch_funding_rates(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetch the current funding rates for multiple markets
             *
             * @see https://docs.kraken.com/api/docs/futures-api/trading/get-$tickers
             *
             * @param {string[]} $symbols unified $market $symbols
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {Order[]} an array of ~@link https://docs.ccxt.com/#/?id=funding-rate-structure funding rate structures~
             */
            Async\await($this->load_markets());
            $marketIds = $this->market_ids($symbols);
            $response = Async\await($this->publicGetTickers ($params));
            $tickers = $this->safe_list($response, 'tickers', array());
            $fundingRates = array();
            for ($i = 0; $i < count($tickers); $i++) {
                $entry = $tickers[$i];
                $entry_symbol = $this->safe_value($entry, 'symbol');
                if ($marketIds !== null) {
                    if (!$this->in_array($entry_symbol, $marketIds)) {
                        continue;
                    }
                }
                $market = $this->safe_market($entry_symbol);
                $parsed = $this->parse_funding_rate($entry, $market);
                $fundingRates[] = $parsed;
            }
            return $this->index_by($fundingRates, 'symbol');
        }) ();
    }

    public function parse_funding_rate($ticker, ?array $market = null): array {
        //
        // {"ask" => 26.283,
        //  "askSize" => 4.6,
        //  "bid" => 26.201,
        //  "bidSize" => 190,
        //  "fundingRate" => -0.000944642727438883,
        //  "fundingRatePrediction" => -0.000872671532340275,
        //  "indexPrice" => 26.253,
        //  "last" => 26.3,
        //  "lastSize" => 0.1,
        //  "lastTime" => "2023-06-11T18:55:28.958Z",
        //  "markPrice" => 26.239,
        //  "open24h" => 26.3,
        //  "openInterest" => 641.1,
        //  "pair" => "COMP:USD",
        //  "postOnly" => False,
        //  "suspended" => False,
        //  "symbol" => "pf_compusd",
        //  "tag" => "perpetual",
        //  "vol24h" => 0.1,
        //  "volumeQuote" => 2.63}
        //
        $fundingRateMultiplier = '8';  // https://support.kraken.com/hc/en-us/articles/9618146737172-Perpetual-Contracts-Funding-Rate-Method-Prior-to-September-29-2022
        $marketId = $this->safe_string($ticker, 'symbol');
        $symbol = $this->symbol($marketId);
        $timestamp = $this->parse8601($this->safe_string($ticker, 'lastTime'));
        $indexPrice = $this->safe_number($ticker, 'indexPrice');
        $markPriceString = $this->safe_string($ticker, 'markPrice');
        $markPrice = $this->parse_number($markPriceString);
        $fundingRateString = $this->safe_string($ticker, 'fundingRate');
        $fundingRateResult = Precise::string_div(Precise::string_mul($fundingRateString, $fundingRateMultiplier), $markPriceString);
        $fundingRate = $this->parse_number($fundingRateResult);
        $nextFundingRateString = $this->safe_string($ticker, 'fundingRatePrediction');
        $nextFundingRateResult = Precise::string_div(Precise::string_mul($nextFundingRateString, $fundingRateMultiplier), $markPriceString);
        $nextFundingRate = $this->parse_number($nextFundingRateResult);
        return array(
            'info' => $ticker,
            'symbol' => $symbol,
            'markPrice' => $markPrice,
            'indexPrice' => $indexPrice,
            'interestRate' => null,
            'estimatedSettlePrice' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'fundingRate' => $fundingRate,
            'fundingTimestamp' => null,
            'fundingDatetime' => null,
            'nextFundingRate' => $nextFundingRate,
            '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 historical funding rate prices
             *
             * @see https://docs.kraken.com/api/docs/futures-api/trading/historical-funding-$rates
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the funding rate history for
             * @param {int} [$since] timestamp in ms of the earliest funding rate to fetch
             * @param {int} [$limit] the maximum amount of ~@link https://docs.ccxt.com/#/?id=funding-rate-history-structure funding rate structures~ to fetch
             * @param {array} [$params] extra parameters specific to the api endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=funding-rate-history-structure funding rate structures~
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' fetchFundingRateHistory() requires a $symbol argument');
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            if (!$market['swap']) {
                throw new BadRequest($this->id . ' fetchFundingRateHistory() supports swap contracts only');
            }
            $request = array(
                'symbol' => strtoupper($market['id']),
            );
            $response = Async\await($this->publicGetHistoricalfundingrates ($this->extend($request, $params)));
            //
            //    {
            //        "rates" => array(
            //          array(
            //            "timestamp" => '2018-08-31T16:00:00.000Z',
            //            "fundingRate" => '2.18900669884E-7',
            //            "relativeFundingRate" => '0.000060779960000000'
            //          ),
            //          ...
            //        )
            //    }
            //
            $rates = $this->safe_value($response, 'rates');
            $result = array();
            for ($i = 0; $i < count($rates); $i++) {
                $item = $rates[$i];
                $datetime = $this->safe_string($item, 'timestamp');
                $result[] = array(
                    'info' => $item,
                    'symbol' => $symbol,
                    'fundingRate' => $this->safe_number($item, 'relativeFundingRate'),
                    'timestamp' => $this->parse8601($datetime),
                    'datetime' => $datetime,
                );
            }
            $sorted = $this->sort_by($result, 'timestamp');
            return $this->filter_by_symbol_since_limit($sorted, $symbol, $since, $limit);
        }) ();
    }

    public function fetch_positions(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             *
             * @see https://docs.kraken.com/api/docs/futures-api/trading/get-open-positions
             *
             * Fetches current contract trading positions
             * @param {string[]} $symbols List of unified $symbols
             * @param {array} [$params] Not used by krakenfutures
             * @return Parsed exchange $response for positions
             */
            Async\await($this->load_markets());
            $request = array();
            $response = Async\await($this->privateGetOpenpositions ($request));
            //
            //    {
            //        "result" => "success",
            //        "openPositions" => array(
            //            {
            //                "side" => "long",
            //                "symbol" => "pi_xrpusd",
            //                "price" => "0.7533",
            //                "fillTime" => "2022-03-03T22:51:16.566Z",
            //                "size" => "230",
            //                "unrealizedFunding" => "-0.001878596918214635"
            //            }
            //        ),
            //        "serverTime" => "2022-03-03T22:51:16.566Z"
            //    }
            //
            $result = $this->parse_positions($response);
            return $this->filter_by_array_positions($result, 'symbol', $symbols, false);
        }) ();
    }

    public function parse_positions($response, ?array $symbols = null, $params = array ()) {
        $result = array();
        $positions = $this->safe_value($response, 'openPositions');
        for ($i = 0; $i < count($positions); $i++) {
            $position = $this->parse_position($positions[$i]);
            $result[] = $position;
        }
        return $result;
    }

    public function parse_position(array $position, ?array $market = null) {
        // cross
        //    {
        //        "side" => "long",
        //        "symbol" => "pi_xrpusd",
        //        "price" => "0.7533",
        //        "fillTime" => "2022-03-03T22:51:16.566Z",
        //        "size" => "230",
        //        "unrealizedFunding" => "-0.001878596918214635"
        //    }
        //
        // isolated
        //    {
        //        "side":"long",
        //        "symbol":"pf_ftmusd",
        //        "price":"0.4921",
        //        "fillTime":"2023-02-22T11:37:16.685Z",
        //        "size":"1",
        //        "unrealizedFunding":"-8.155240068885155E-8",
        //        "pnlCurrency":"USD",
        //        "maxFixedLeverage":"1.0"
        //    }
        //
        $leverage = $this->safe_number($position, 'maxFixedLeverage');
        $marginType = 'cross';
        if ($leverage !== null) {
            $marginType = 'isolated';
        }
        $datetime = $this->safe_string($position, 'fillTime');
        $marketId = $this->safe_string($position, 'symbol');
        $market = $this->safe_market($marketId, $market);
        return array(
            'info' => $position,
            'symbol' => $market['symbol'],
            'timestamp' => $this->parse8601($datetime),
            'datetime' => $datetime,
            'initialMargin' => null,
            'initialMarginPercentage' => null,
            'maintenanceMargin' => null,
            'maintenanceMarginPercentage' => null,
            'entryPrice' => $this->safe_number($position, 'price'),
            'notional' => null,
            'leverage' => $leverage,
            'unrealizedPnl' => null,
            'contracts' => $this->safe_number($position, 'size'),
            'contractSize' => $this->safe_number($market, 'contractSize'),
            'marginRatio' => null,
            'liquidationPrice' => null,
            'markPrice' => null,
            'collateral' => null,
            'marginType' => $marginType,
            'side' => $this->safe_string($position, 'side'),
            'percentage' => null,
        );
    }

    public function fetch_leverage_tiers(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * retrieve information on the maximum leverage, and maintenance margin for trades of varying trade sizes
             *
             * @see https://docs.kraken.com/api/docs/futures-api/trading/get-instruments
             *
             * @param {string[]|null} $symbols list of unified market $symbols
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=leverage-tiers-structure leverage tiers structures~, indexed by market $symbols
             */
            Async\await($this->load_markets());
            $response = Async\await($this->publicGetInstruments ($params));
            //
            //    {
            //        "result" => "success",
            //        "instruments" => array(
            //            {
            //                "symbol" => "fi_ethusd_180928",
            //                "type" => "futures_inverse",  // futures_vanilla  // spot index
            //                "underlying" => "rr_ethusd",
            //                "lastTradingTime" => "2018-09-28T15:00:00.000Z",
            //                "tickSize" => 0.1,
            //                "contractSize" => 1,
            //                "tradeable" => true,
            //                "marginLevels" => array(
            //                    array(
            //                        "contracts":0,
            //                        "initialMargin":0.02,
            //                        "maintenanceMargin":0.01
            //                    ),
            //                    array(
            //                        "contracts":250000,
            //                        "initialMargin":0.04,
            //                        "maintenanceMargin":0.02
            //                    ),
            //                    ...
            //                ),
            //                "isin" => "GB00JVMLMP88",
            //                "retailMarginLevels" => array(
            //                    array(
            //                        "contracts" => 0,
            //                        "initialMargin" => 0.5,
            //                        "maintenanceMargin" => 0.25
            //                    }
            //                ),
            //                "tags" => array(),
            //            ),
            //            {
            //                "symbol" => "in_xbtusd",
            //                "type" => "spot index",
            //                "tradeable":false
            //            }
            //        )
            //        "serverTime" => "2018-07-19T11:32:39.433Z"
            //    }
            //
            $data = $this->safe_list($response, 'instruments');
            return $this->parse_leverage_tiers($data, $symbols, 'symbol');
        }) ();
    }

    public function parse_market_leverage_tiers($info, ?array $market = null): array {
        /**
         * @ignore
         * @param $info Exchange $market response for 1 $market
         * @param $market CCXT $market
         */
        //
        //    {
        //        "symbol" => "fi_ethusd_180928",
        //        "type" => "futures_inverse",  // futures_vanilla  // spot index
        //        "underlying" => "rr_ethusd",
        //        "lastTradingTime" => "2018-09-28T15:00:00.000Z",
        //        "tickSize" => 0.1,
        //        "contractSize" => 1,
        //        "tradeable" => true,
        //        "marginLevels" => array(
        //            array(
        //                "contracts":0,
        //                "initialMargin":0.02,
        //                "maintenanceMargin":0.01
        //            ),
        //            array(
        //                "contracts":250000,
        //                "initialMargin":0.04,
        //                "maintenanceMargin":0.02
        //            ),
        //            ...
        //        ),
        //        "isin" => "GB00JVMLMP88",
        //        "retailMarginLevels" => array(
        //            {
        //                "contracts" => 0,
        //                "initialMargin" => 0.5,
        //                "maintenanceMargin" => 0.25
        //            }
        //        ),
        //        "tags" => array(),
        //    }
        //
        $marginLevels = $this->safe_value($info, 'marginLevels');
        $marketId = $this->safe_string($info, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $tiers = array();
        if ($marginLevels === null) {
            return $tiers;
        }
        for ($i = 0; $i < count($marginLevels); $i++) {
            $tier = $marginLevels[$i];
            $initialMargin = $this->safe_string($tier, 'initialMargin');
            $minNotional = $this->safe_number($tier, 'numNonContractUnits');
            if ($i !== 0) {
                $tiersLength = count($tiers);
                $previousTier = $tiers[$tiersLength - 1];
                $previousTier['maxNotional'] = $minNotional;
            }
            $tiers[] = array(
                'tier' => $this->sum($i, 1),
                'symbol' => $this->safe_symbol($marketId, $market),
                'currency' => $market['quote'],
                'minNotional' => $minNotional,
                'maxNotional' => null,
                'maintenanceMarginRate' => $this->safe_number($tier, 'maintenanceMargin'),
                'maxLeverage' => $this->parse_number(Precise::string_div('1', $initialMargin)),
                'info' => $tier,
            );
        }
        return $tiers;
    }

    public function parse_transfer(array $transfer, ?array $currency = null): array {
        //
        // $transfer
        //
        //    {
        //        "result" => "success",
        //        "serverTime" => "2022-04-12T01:22:53.420Z"
        //    }
        //
        $datetime = $this->safe_string($transfer, 'serverTime');
        return array(
            'info' => $transfer,
            'id' => null,
            'timestamp' => $this->parse8601($datetime),
            'datetime' => $datetime,
            'currency' => $this->safe_string($currency, 'code'),
            'amount' => null,
            'fromAccount' => null,
            'toAccount' => null,
            'status' => $this->safe_string($transfer, 'result'),
        );
    }

    public function parse_account($account) {
        $accountByType = array(
            'main' => 'cash',
            'funding' => 'cash',
            'future' => 'cash',
            'futures' => 'cash',
            'cashAccount' => 'cash',
            'multiCollateralMarginAccount' => 'flex',
            'multiCollateral' => 'flex',
            'multiCollateralMargin' => 'flex',
        );
        if (is_array($accountByType) && array_key_exists($account, $accountByType)) {
            return $accountByType[$account];
        } elseif (is_array($this->markets) && array_key_exists($account, $this->markets)) {
            $market = $this->market($account);
            $marketId = $market['id'];
            $splitId = explode('_', $marketId);
            if ($market['inverse']) {
                return 'fi_' . $this->safe_string($splitId, 1);
            } else {
                return 'fv_' . $this->safe_string($splitId, 1);
            }
        } else {
            return $account;
        }
    }

    public function transfer_out(string $code, $amount, $params = array ()) {
        return Async\async(function () use ($code, $amount, $params) {
            /**
             * transfer from futures wallet to spot wallet
             * @param {str} $code Unified currency $code
             * @param {float} $amount Size of the transfer
             * @param {dict} [$params] Exchange specific parameters
             * @return a ~@link https://docs.ccxt.com/#/?id=transfer-structure transfer structure~
             */
            return Async\await($this->transfer($code, $amount, 'future', 'spot', $params));
        }) ();
    }

    public function transfer(string $code, float $amount, string $fromAccount, string $toAccount, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $amount, $fromAccount, $toAccount, $params) {
            /**
             *
             * @see https://docs.kraken.com/api/docs/futures-api/trading/transfer
             * @see https://docs.kraken.com/api/docs/futures-api/trading/sub-account-$transfer
             *
             * transfers currencies between sub-accounts
             * @param {string} $code Unified $currency $code
             * @param {float} $amount Size of the $transfer
             * @param {string} $fromAccount 'main'/'funding'/'future', 'flex', or a unified market symbol
             * @param {string} $toAccount 'main'/'funding', 'flex', 'spot' or a unified market symbol
             * @param {array} [$params] Exchange specific parameters
             * @return a ~@link https://docs.ccxt.com/#/?id=$transfer-structure $transfer structure~
             */
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            if ($fromAccount === 'spot') {
                throw new BadRequest($this->id . ' $transfer does not yet support transfers from spot');
            }
            $request = array(
                'amount' => $amount,
            );
            $response = null;
            if ($toAccount === 'spot') {
                if ($this->parse_account($fromAccount) !== 'cash') {
                    throw new BadRequest($this->id . ' $transfer cannot $transfer from ' . $fromAccount . ' to ' . $toAccount);
                }
                $request['currency'] = $currency['id'];
                $response = Async\await($this->privatePostWithdrawal ($this->extend($request, $params)));
            } else {
                $request['fromAccount'] = $this->parse_account($fromAccount);
                $request['toAccount'] = $this->parse_account($toAccount);
                $request['unit'] = $currency['id'];
                $response = Async\await($this->privatePostTransfer ($this->extend($request, $params)));
            }
            //
            //    {
            //        "result" => "success",
            //        "serverTime" => "2022-04-12T01:22:53.420Z"
            //    }
            //
            $transfer = $this->parse_transfer($response, $currency);
            return $this->extend($transfer, array(
                'amount' => $amount,
                'fromAccount' => $fromAccount,
                'toAccount' => $toAccount,
            ));
        }) ();
    }

    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://docs.kraken.com/api/docs/futures-api/trading/set-$leverage-setting
             *
             * @param {float} $leverage the rate of $leverage
             * @param {string} $symbol unified market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} response from the exchange
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' setLeverage() requires a $symbol argument');
            }
            Async\await($this->load_markets());
            $request = array(
                'maxLeverage' => $leverage,
                'symbol' => strtoupper($this->market_id($symbol)),
            );
            //
            // array( result => "success", serverTime => "2023-08-01T09:40:32.345Z" )
            //
            return Async\await($this->privatePutLeveragepreferences ($this->extend($request, $params)));
        }) ();
    }

    public function fetch_leverages(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetch the set leverage for all contract and margin markets
             *
             * @see https://docs.kraken.com/api/docs/futures-api/trading/get-leverage-setting
             *
             * @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());
            $response = Async\await($this->privateGetLeveragepreferences ($params));
            //
            //     {
            //         "result" => "success",
            //         "serverTime" => "2024-03-06T02:35:46.336Z",
            //         "leveragePreferences" => array(
            //             array(
            //                 "symbol" => "PF_ETHUSD",
            //                 "maxLeverage" => 30.00
            //             ),
            //         )
            //     }
            //
            $leveragePreferences = $this->safe_list($response, 'leveragePreferences', array());
            return $this->parse_leverages($leveragePreferences, $symbols, 'symbol');
        }) ();
    }

    public function fetch_leverage(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetch the set leverage for a $market
             *
             * @see https://docs.kraken.com/api/docs/futures-api/trading/get-leverage-setting
             *
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=leverage-structure leverage structure~
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' fetchLeverage() requires a $symbol argument');
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'symbol' => strtoupper($this->market_id($symbol)),
            );
            $response = Async\await($this->privateGetLeveragepreferences ($this->extend($request, $params)));
            //
            //     {
            //         "result" => "success",
            //         "serverTime" => "2023-08-01T09:54:08.900Z",
            //         "leveragePreferences" => array( array( $symbol => "PF_LTCUSD", maxLeverage => "5.00" ) )
            //     }
            //
            $leveragePreferences = $this->safe_list($response, 'leveragePreferences', array());
            $data = $this->safe_dict($leveragePreferences, 0, array());
            return $this->parse_leverage($data, $market);
        }) ();
    }

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

    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);
        }
        $errors = $this->safe_value($response, 'errors');
        $firstError = $this->safe_value($errors, 0);
        $firtErrorMessage = $this->safe_string($firstError, 'message');
        $message = $this->safe_string($response, 'error', $firtErrorMessage);
        if ($message === null) {
            return null;
        }
        $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
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $apiVersions = $this->safe_value($this->options['versions'], $api, array());
        $methodVersions = $this->safe_value($apiVersions, $method, array());
        $defaultVersion = $this->safe_string($methodVersions, $path, $this->version);
        $version = $this->safe_string($params, 'version', $defaultVersion);
        $params = $this->omit($params, 'version');
        $apiAccess = $this->safe_value($this->options['access'], $api, array());
        $methodAccess = $this->safe_value($apiAccess, $method, array());
        $access = $this->safe_string($methodAccess, $path, 'public');
        $endpoint = $version . '/' . $this->implode_params($path, $params);
        $params = $this->omit($params, $this->extract_params($path));
        $query = $endpoint;
        $postData = '';
        if ($path === 'batchorder') {
            $postData = 'json=' . $this->json($params);
            $body = $postData;
        } elseif ($params) {
            $postData = $this->urlencode($params);
            $query .= '?' . $postData;
        }
        $url = $this->urls['api'][$api] . $query;
        if ($api === 'private' || $access === 'private') {
            $this->check_required_credentials();
            $auth = $postData . '/api/';
            if ($api !== 'private') {
                $auth .= $api . '/';
            }
            $auth .= $endpoint; // 1
            $hash = $this->hash($this->encode($auth), 'sha256', 'binary'); // 2
            $secret = base64_decode($this->secret); // 3
            $signature = $this->hmac($hash, $secret, 'sha512', 'base64'); // 4-5
            $headers = array(
                'Content-Type' => 'application/x-www-form-urlencoded',
                'Accept' => 'application/json',
                'APIKey' => $this->apiKey,
                'Authent' => $signature,
            );
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }
}
