# -*- coding: utf-8 -*-

# 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

from ccxt.base.exchange import Exchange
import hashlib
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import OrderImmediatelyFillable
from ccxt.base.errors import OrderNotFillable
from ccxt.base.errors import DuplicateOrderId
from ccxt.base.errors import DDoSProtection
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.errors import InvalidNonce
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class krakenfutures(Exchange):

    def describe(self):
        return self.deep_extend(super(krakenfutures, self).describe(), {
            'id': 'krakenfutures',
            'name': 'Kraken Futures',
            'countries': ['US'],
            'version': 'v3',
            'userAgent': None,
            'rateLimit': 600,
            'has': {
                'CORS': None,
                'spot': False,
                'margin': False,
                'swap': True,
                'future': True,
                'option': False,
                'cancelAllOrders': True,
                'cancelOrder': True,
                'createMarketOrder': False,
                'createOrder': True,
                'editOrder': True,
                'fetchBalance': True,
                'fetchBorrowRate': False,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchBorrowRates': False,
                'fetchBorrowRatesPerSymbol': False,
                'fetchClosedOrders': None,  # https://support.kraken.com/hc/en-us/articles/360058243651-Historical-orders
                'fetchFundingHistory': None,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': True,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchIsolatedPositions': False,
                '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,
                'setLeverage': False,
                'setMarginMode': False,
                'transfer': True,
            },
            'urls': {
                'test': {
                    'public': 'https://demo-futures.kraken.com/derivatives',
                    'private': 'https://demo-futures.kraken.com/derivatives',
                    'www': 'https://demo-futures.kraken.com',
                },
                'logo': 'https://user-images.githubusercontent.com/24300605/81436764-b22fd580-9172-11ea-9703-742783e6376d.jpg',
                'api': {
                    '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': [
                    'https://support.kraken.com/hc/en-us/categories/360001806372-Futures-API',
                ],
                'fees': 'https://support.kraken.com/hc/en-us/articles/360022835771-Transaction-fees-and-rebates-for-Kraken-Futures',
                'referral': None,
            },
            'api': {
                'public': {
                    'get': [
                        'instruments',
                        'orderbook',
                        'tickers',
                        'history',
                        'historicalfundingrates',
                    ],
                },
                'private': {
                    'get': [
                        'openpositions',
                        'notifications',
                        'accounts',
                        'openorders',
                        'recentorders',
                        'fills',
                        'transfers',
                    ],
                    'post': [
                        'sendorder',
                        'editorder',
                        'cancelorder',
                        'transfer',
                        'batchorder',
                        'cancelallorders',
                        'cancelallordersafter',
                        'withdrawal',                              # for futures wallet -> kraken spot wallet
                    ],
                },
                'charts': {
                    'get': [
                        '{price_type}/{symbol}/{interval}',
                    ],
                },
                'history': {
                    'get': [
                        'orders',
                        'executions',
                        'triggers',
                        'accountlogcsv',
                        'market/{symbol}/orders',
                        'market/{symbol}/executions',
                    ],
                },
                'feeschedules': {
                    'get': [
                        'volumes',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'tierBased': False,
                    'percentage': True,
                    'maker': self.parse_number('-0.0002'),
                    'taker': self.parse_number('0.00075'),
                },
            },
            'exceptions': {
                'exact': {
                    'apiLimitExceeded': RateLimitExceeded,
                    'marketUnavailable': ExchangeNotAvailable,
                    'requiredArgumentMissing': BadRequest,
                    'unavailable': ExchangeNotAvailable,
                    'authenticationError': AuthenticationError,
                    'accountInactive': ExchangeError,              # When account has no trade history / no order history. Should self error be ignored in some cases?
                    'invalidAccount': BadRequest,                  # the fromAccount or the toAccount are invalid
                    'invalidAmount': BadRequest,
                    'insufficientFunds': InsufficientFunds,
                    'Bad Request': BadRequest,                     # The URL contains invalid characters.(Please encode the json URL parameter)
                    'Unavailable': InsufficientFunds,              # Insufficient funds in Futures account [withdraw]
                },
                'broad': {
                    'invalidArgument': BadRequest,
                    'nonceBelowThreshold': InvalidNonce,
                    'nonceDuplicate': InvalidNonce,
                },
            },
            'precisionMode': TICK_SIZE,
            'options': {
                'access': {
                    'history': {
                        'GET': {
                            'orders': 'private',
                            'executions': 'private',
                            'triggers': 'private',
                            'accountlogcsv': 'private',
                        },
                    },
                },
                'settlementCurrencies': {
                    'flex': ['USDT', 'BTC', 'USD', 'GBP', 'EUR', 'USDC'],
                },
                'symbol': {
                    'quoteIds': ['USD', 'XBT'],
                    'reversed': False,
                },
                'versions': {
                    'public': {
                        'GET': {
                            'historicalfundingrates': 'v4',
                        },
                    },
                    'charts': {
                        'GET': {
                            '{price_type}/{symbol}/{interval}': 'v1',
                        },
                    },
                    'history': {
                        'GET': {
                            'orders': 'v2',
                            'executions': 'v2',
                            'triggers': 'v2',
                            'accountlogcsv': 'v2',
                        },
                    },
                },
            },
            'timeframes': {
                '1m': '1m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '4h': '4h',
                '12h': '12h',
                '1d': '1d',
                '1w': '1w',
            },
        })

    def fetch_markets(self, params={}):
        """
        Fetches the available trading markets from the exchange, Multi-collateral markets are returned as linear markets, but can be settled in multiple currencies
        see https://docs.futures.kraken.com/#http-api-trading-v3-api-instrument-details-get-instruments
        :param dict params: exchange specific params
        :returns: An array of market structures
        """
        response = self.publicGetInstruments(params)
        #
        #    {
        #        "result": "success",
        #        "instruments": [
        #            {
        #                "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": [
        #                    {
        #                        "contracts":0,
        #                        "initialMargin":0.02,
        #                        "maintenanceMargin":0.01
        #                    },
        #                    {
        #                        "contracts":250000,
        #                        "initialMargin":0.04,
        #                        "maintenanceMargin":0.02
        #                    },
        #                    ...
        #                ],
        #                "isin": "GB00JVMLMP88",
        #                "retailMarginLevels": [
        #                    {
        #                        "contracts": 0,
        #                        "initialMargin": 0.5,
        #                        "maintenanceMargin": 0.25
        #                    }
        #                ],
        #                "tags": [],
        #            },
        #            {
        #                "symbol": "in_xbtusd",
        #                "type": "spot index",
        #                "tradeable":false
        #            }
        #        ]
        #        "serverTime": "2018-07-19T11:32:39.433Z"
        #    }
        #
        instruments = self.safe_value(response, 'instruments', [])
        result = []
        for i in range(0, len(instruments)):
            market = instruments[i]
            id = self.safe_string(market, 'symbol')
            marketType = self.safe_string(market, 'type')
            type = None
            index = (marketType.find(' index') >= 0)
            linear = None
            inverse = None
            expiry = None
            if not index:
                linear = (marketType.find('_vanilla') >= 0)
                inverse = not linear
                settleTime = self.safe_string(market, 'lastTradingTime')
                type = 'swap' if (settleTime is None) else 'future'
                expiry = self.parse8601(settleTime)
            else:
                type = 'index'
            swap = (type == 'swap')
            future = (type == 'future')
            symbol = id
            split = id.split('_')
            splitMarket = self.safe_string(split, 1)
            baseId = splitMarket.replace('usd', '')
            quoteId = 'usd'  # always USD
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            # swap == perpetual
            settle = None
            settleId = None
            contract = (swap or future)
            if contract:
                exchangeType = self.safe_string(market, 'type')
                if exchangeType == 'futures_inverse':
                    settle = base
                    settleId = baseId
                    inverse = True
                else:
                    settle = quote
                    settleId = quoteId
                    inverse = False
                linear = not inverse
                symbol = base + '/' + quote + ':' + settle
                if future:
                    symbol = symbol + '-' + self.yymmdd(expiry)
            result.append({
                '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': None,
                'contract': contract,
                'linear': linear,
                'inverse': inverse,
                'contractSize': self.safe_number(market, 'contractSize'),
                'maintenanceMarginRate': None,
                'expiry': expiry,
                'expiryDatetime': self.iso8601(expiry),
                'strike': None,
                'optionType': None,
                'precision': {
                    'amount': None if index else self.parse_number('1'),
                    'price': self.safe_number(market, 'tickSize'),
                },
                'limits': {
                    'leverage': {
                        'min': None,
                        'max': None,
                    },
                    'amount': {
                        'min': None,
                        'max': None,
                    },
                    'price': {
                        'min': None,
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                },
                'info': market,
            })
        settlementCurrencies = self.options['settlementCurrencies']['flex']
        currencies = []
        for i in range(0, len(settlementCurrencies)):
            code = settlementCurrencies[i]
            currencies.append({
                'id': code.lower(),
                'numericId': None,
                'code': code,
                'precision': None,
            })
        self.currencies = self.deep_extend(currencies, self.currencies)
        return result

    def fetch_order_book(self, symbol, limit=None, params={}):
        """
        Fetches a list of open orders in a market
        :param str symbol: Unified market symbol
        :param int|None limit: Not used by krakenfutures
        :param dict params: exchange specific params
        :returns: An `order book structure <https://docs.ccxt.com/en/latest/manual.html#order-book-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        response = self.publicGetOrderbook(self.extend(request, params))
        #
        #    {
        #       "result": "success",
        #       "serverTime": "2016-02-25T09:45:53.818Z",
        #       "orderBook": {
        #          "bids": [
        #                [
        #                    4213,
        #                    2000,
        #                ],
        #                [
        #                    4210,
        #                    4000,
        #                ],
        #                ...
        #            ],
        #            "asks": [
        #                [
        #                    4218,
        #                    4000,
        #                ],
        #                [
        #                    4220,
        #                    5000,
        #                ],
        #                ...
        #            ],
        #        },
        #    }
        #
        timestamp = self.parse8601(response['serverTime'])
        return self.parse_order_book(response['orderBook'], symbol, timestamp)

    def fetch_tickers(self, symbols=None, params={}):
        self.load_markets()
        response = self.publicGetTickers(params)
        #
        #    {
        #        result: 'success',
        #        tickers: [
        #            {
        #                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'
        #            },
        #            ...
        #        ],
        #        serverTime: '2022-02-18T14:16:29.440Z'
        #    }
        #
        tickers = self.safe_value(response, 'tickers')
        return self.parse_tickers(tickers, symbols)

    def parse_ticker(self, ticker, market=None):
        #
        #    {
        #        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 = self.safe_string(ticker, 'symbol')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        timestamp = self.parse8601(self.safe_string(ticker, 'lastTime'))
        open = self.safe_string(ticker, 'open24h')
        last = self.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 = self.safe_string(ticker, 'vol24h')
        baseVolume = None
        quoteVolume = None
        isIndex = self.safe_value(market, 'index', False)
        if not isIndex:
            if market['linear']:
                baseVolume = volume
            elif market['inverse']:
                quoteVolume = volume
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': None,
            'low': None,
            'bid': self.safe_string(ticker, 'bid'),
            'bidVolume': self.safe_string(ticker, 'bidSize'),
            'ask': self.safe_string(ticker, 'ask'),
            'askVolume': self.safe_string(ticker, 'askSize'),
            'vwap': None,
            'open': open,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': change,
            'percentage': percentage,
            'average': average,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        })

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
            'price_type': self.safe_string(params, 'price', 'trade'),
            'interval': self.timeframes[timeframe],
        }
        params = self.omit(params, 'price')
        if since is not None:
            duration = self.parse_timeframe(timeframe)
            request['from'] = int(since / 1000)
            if limit is None:
                limit = 5000
            elif limit > 5000:
                raise BadRequest(self.id + ' fetchOHLCV() limit cannot exceed 5000')
            toTimestamp = self.sum(request['from'], limit * duration - 1)
            currentTimestamp = self.seconds()
            request['to'] = min(toTimestamp, currentTimestamp)
        elif limit is not None:
            if limit > 5000:
                raise BadRequest(self.id + ' fetchOHLCV() limit cannot exceed 5000')
            duration = self.parse_timeframe(timeframe)
            request['to'] = self.seconds()
            request['from'] = int(request['to'] - (duration * limit))
        response = self.chartsGetPriceTypeSymbolInterval(self.extend(request, params))
        #
        #    {
        #        "candles": [
        #            {
        #                "time": 1645198500000,
        #                "open": "309.15000000000",
        #                "high": "309.15000000000",
        #                "low": "308.70000000000",
        #                "close": "308.85000000000",
        #                "volume": 0
        #            }
        #        ],
        #        "more_candles": True
        #    }
        #
        candles = self.safe_value(response, 'candles')
        return self.parse_ohlcvs(candles, market, timeframe, since, limit)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #    {
        #        "time": 1645198500000,
        #        "open": "309.15000000000",
        #        "high": "309.15000000000",
        #        "low": "308.70000000000",
        #        "close": "308.85000000000",
        #        "volume": 0
        #    }
        #
        return [
            self.safe_integer(ohlcv, 'time'),       # unix timestamp in milliseconds
            self.safe_number(ohlcv, 'open'),        # open price
            self.safe_number(ohlcv, 'high'),        # highest price
            self.safe_number(ohlcv, 'low'),         # lowest price
            self.safe_number(ohlcv, 'close'),       # close price
            self.safe_number(ohlcv, 'volume'),      # trading volume, None for mark or index price
        ]

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        """
         * @descriptions Fetch a history of filled trades that self account has made
        :param str symbol: Unified CCXT market symbol
        :param int|None since: Timestamp in ms of earliest trade. Not used by krakenfutures except in combination with params.until
        :param int|None limit: Total number of trades, cannot exceed 100
        :param dict params: Exchange specific params
        :param int|None params['until']: Timestamp in ms of latest trade
        :returns: An array of `trade structures <https://docs.ccxt.com/en/latest/manual.html#trade-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        until = self.safe_integer(params, 'until')
        if until is not None:
            request['lastTime'] = self.iso8601(until)
        #
        #    {
        #        "result": "success",
        #        "history": [
        #            {
        #                "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"
        #    }
        #
        response = self.publicGetHistory(self.extend(request, params))
        history = self.safe_value(response, 'history')
        return self.parse_trades(history, market, since, limit)

    def parse_trade(self, trade, market=None):
        #
        # fetchTrades(public)
        #
        #    {
        #        "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"
        #    }
        #
        # 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": {
        #            "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 = self.parse8601(self.safe_string_2(trade, 'time', 'fillTime'))
        price = self.safe_string(trade, 'price')
        amount = self.safe_string_2(trade, 'size', 'amount', '0.0')
        id = self.safe_string_2(trade, 'uid', 'fill_id')
        if id is None:
            id = self.safe_string(trade, 'executionId')
        order = self.safe_string(trade, 'order_id')
        symbolId = self.safe_string(trade, 'symbol')
        side = self.safe_string(trade, 'side')
        type = None
        priorEdit = self.safe_value(trade, 'orderPriorEdit')
        priorExecution = self.safe_value(trade, 'orderPriorExecution')
        if priorExecution is not None:
            order = self.safe_string(priorExecution, 'orderId')
            symbolId = self.safe_string(priorExecution, 'symbol')
            side = self.safe_string(priorExecution, 'side')
            type = self.safe_string(priorExecution, 'type')
        elif priorEdit is not None:
            order = self.safe_string(priorEdit, 'orderId')
            symbolId = self.safe_string(priorEdit, 'symbol')
            side = self.safe_string(priorEdit, 'type')
            type = self.safe_string(priorEdit, 'type')
        if type is not None:
            type = self.parse_order_type(type)
        symbol = None
        if symbolId is not None:
            market = self.safe_value(self.markets_by_id, symbolId)
            if market is None:
                symbol = symbolId
        symbol = self.safe_string(market, 'symbol', symbol)
        cost = None
        if (amount is not None) and (price is not None) and (market is not None):
            linear = self.safe_value(market, 'linear')
            if linear:
                cost = Precise.string_mul(amount, price)  # in quote
            else:
                cost = Precise.string_div(amount, price)  # in base
            contractSize = self.safe_string(market, 'contractSize')
            cost = Precise.string_mul(cost, contractSize)
        takerOrMaker = None
        fillType = self.safe_string(trade, 'fillType')
        if fillType is not None:
            if fillType.find('taker') >= 0:
                takerOrMaker = 'taker'
            elif fillType.find('maker') >= 0:
                takerOrMaker = 'maker'
        return self.safe_trade({
            'info': trade,
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'order': order,
            'type': type,
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': None,
        })

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        """
        Create an order on the exchange
        :param str symbol: market symbol
        :param str type: One of 'limit', 'market', 'take_profit'
        :param str side: buy or sell
        :param int amount: Contract quantity
        :param float price: Limit order price
        :param float|None params['stopPrice']: The stop price associated with a stop or take profit order, Required if orderType is stp or take_profit, Must not have more than 2 decimal places, Note that for stop orders, limitPrice denotes the worst price at which the stop or take_profit order can get filled at. If no limitPrice is provided the stop or take_profit order will trigger a market order,
        :param bool|None params['reduceOnly']: Set as True if you wish the order to only reduce an existing position, Any order which increases an existing position will be rejected, Default False,
        :param bool|None params['postOnly']: Set as True if you wish to make a postOnly order, Default False
        :param str|None params['triggerSignal']: If placing a stp or take_profit, the signal used for trigger, One of: 'mark', 'index', 'last', last is market price
        :param str|None params['cliOrdId']: UUID The order identity that is specified from the user, It must be globally unique
        """
        self.load_markets()
        type = self.safe_string(params, 'orderType', type)
        timeInForce = self.safe_string(params, 'timeInForce')
        stopPrice = self.safe_string(params, 'stopPrice')
        postOnly = self.safe_string(params, 'postOnly')
        if (type == 'stp' or type == 'take_profit') and stopPrice is None:
            raise ArgumentsRequired(self.id + ' createOrder requires params.stopPrice when type is ' + type)
        if stopPrice is not None:
            type = 'stp'
        elif postOnly:
            type = 'postOnly'
        elif timeInForce == 'ioc':
            type = 'ioc'
        elif type == 'limit':
            type = 'lmt'
        elif type == 'market':
            type = 'mkt'
        request = {
            'orderType': type,
            'symbol': self.market_id(symbol),
            'side': side,
            'size': amount,
        }
        if price is not None:
            request['limitPrice'] = price
        response = self.privatePostSendorder(self.extend(request, params))
        #
        #    {
        #        "result": "success",
        #        "sendStatus": {
        #            "order_id": "salf320-e337-47ac-b345-30sdfsalj",
        #            "status": "placed",
        #            "receivedTime": "2022-02-28T19:32:17.122Z",
        #            "orderEvents": [
        #                {
        #                    "order": {
        #                        "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 = self.safe_value(response, 'sendStatus')
        status = self.safe_string(sendStatus, 'status')
        self.verify_order_action_success(status, 'createOrder', ['filled'])
        return self.parse_order(sendStatus)

    def edit_order(self, id, symbol, type, side, amount=None, price=None, params={}):
        """
        Edit an open order on the exchange
        :param str id: order id
        :param str symbol: Not used by Krakenfutures
        :param str type: Not used by Krakenfutures
        :param str side: Not used by Krakenfutures
        :param float|None amount: Order size
        :param float|None price: Price to fill order at
        :param dict params: Exchange specific params
        :returns: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        request = {
            'orderId': id,
        }
        if amount is not None:
            request['size'] = amount
        if price is not None:
            request['limitPrice'] = price
        response = self.privatePostEditorder(self.extend(request, params))
        status = self.safe_string(response['editStatus'], 'status')
        self.verify_order_action_success(status, 'editOrder', ['filled'])
        order = self.parse_order(response['editStatus'])
        return self.extend({'info': response}, order)

    def cancel_order(self, id, symbol=None, params={}):
        """
        :param str id: Order id
        :param str|None symbol: Not used by Krakenfutures
        :param dict params: Exchange specific params
        :returns: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        response = self.privatePostCancelorder(self.extend({'order_id': id}, params))
        status = self.safe_string(self.safe_value(response, 'cancelStatus', {}), 'status')
        self.verify_order_action_success(status, 'cancelOrder')
        order = {}
        if 'cancelStatus' in response:
            order = self.parse_order(response['cancelStatus'])
        return self.extend({'info': response}, order)

    def cancel_all_orders(self, symbol=None, params={}):
        """
        Cancels all orders on the exchange, including trigger orders
        :param str symbol: Unified market symbol
        :param dict params: Exchange specific params
        :returns: Response from exchange api
        """
        request = {}
        if symbol is not None:
            request['symbol'] = self.market_id(symbol)
        response = self.privatePostCancelallorders(self.extend(request, params))
        return response

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        Gets all open orders, including trigger orders, for an account from the exchange api
        :param str 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 dict params: Exchange specific parameters
        :returns: An array of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        response = self.privateGetOpenorders(params)
        orders = self.safe_value(response, 'openOrders', [])
        return self.parse_orders(orders, market, since, limit)

    def parse_order_type(self, orderType):
        map = {
            'lmt': 'limit',
            'mkt': 'market',
            'post': 'limit',
            'ioc': 'market',
        }
        return self.safe_string(map, orderType, orderType)

    def verify_order_action_success(self, status, method, omit=[]):
        errors = {
            'invalidOrderType': InvalidOrder,
            'invalidSide': InvalidOrder,
            'invalidSize': InvalidOrder,
            'invalidPrice': InvalidOrder,
            'insufficientAvailableFunds': InsufficientFunds,
            'selfFill': ExchangeError,
            'tooManySmallOrders': ExchangeError,
            'maxPositionViolation': BadRequest,
            'marketSuspended': ExchangeNotAvailable,
            'marketInactive': ExchangeNotAvailable,
            'clientOrderIdAlreadyExist': DuplicateOrderId,
            'clientOrderIdTooLong': BadRequest,
            'outsidePriceCollar': InvalidOrder,
            'postWouldExecute': OrderImmediatelyFillable,  # the unplaced order could actually be parsed(with status = "rejected"), but there is self specific error for self
            'iocWouldNotExecute': OrderNotFillable,  # -||-
            'wouldNotReducePosition': ExchangeError,
            'orderForEditNotFound': OrderNotFound,
            'orderForEditNotAStop': InvalidOrder,
            'filled': OrderNotFound,
            'notFound': OrderNotFound,
        }
        if (status in errors) and not self.in_array(status, omit):
            raise errors[status](self.id + ': ' + method + ' failed due to ' + status)

    def parse_order_status(self, status):
        statuses = {
            '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 position in self 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 self.safe_string(statuses, status, status)

    def parse_order(self, order, market=None):
        #
        # LIMIT
        #
        #    {
        #        "order_id": "179f9af8-e45e-469d-b3e9-2fd4675cb7d0",
        #        "status": "placed",
        #        "receivedTime": "2019-09-05T16:33:50.734Z",
        #        "orderEvents": [
        #            {
        #                "uid": "614a5298-0071-450f-83c6-0617ce8c6bc4",
        #                "order": {
        #                    "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": [
        #            {
        #                "orderTrigger": {
        #                    "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": [
        #            {
        #                "executionId": "e1ec9f63-2338-4c44-b40a-43486c6732d7",
        #                "price": 7244.5,
        #                "amount": 10,
        #                "orderPriorEdit": null,
        #                "orderPriorExecution": {
        #                    "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": [
        #            {
        #                "old": {
        #                    "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": {
        #                    "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": [
        #            {
        #                "uid": "85c40002-3f20-4e87-9302-262626c3531b",
        #                "order": {
        #                    "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"
        #            }
        #        ]
        #    }
        #
        # 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"
        #    }
        #
        orderEvents = self.safe_value(order, 'orderEvents', [])
        details = None
        isPrior = False
        fixed = False
        statusId = None
        price = None
        trades = []
        if len(orderEvents) > 0:
            executions = []
            for i in range(0, len(orderEvents)):
                item = orderEvents[i]
                if self.safe_string(item, 'type') == 'EXECUTION':
                    executions.append(item)
                # Final order(after placement / editing / execution / canceling)
                orderTrigger = self.safe_value(item, 'orderTrigger')
                details = self.safe_value_2(item, 'new', 'order', orderTrigger)
                if details is not None:
                    isPrior = False
                    fixed = True
                elif not fixed:
                    orderPriorExecution = self.safe_value(item, 'orderPriorExecution')
                    details = self.safe_value_2(item, 'orderPriorExecution', 'orderPriorEdit')
                    price = self.safe_string(orderPriorExecution, 'limitPrice')
                    if details is not None:
                        isPrior = True
            trades = self.parse_trades(executions)
            statusId = self.safe_string(order, 'status')
        if details is None:
            details = order
        if statusId is None:
            statusId = self.safe_string(details, 'status')
        # This may be incorrectly marked as "open" if only execution report is given,
        # but will be fixed below
        status = self.parse_order_status(statusId)
        isClosed = self.in_array(status, ['canceled', 'rejected', 'closed'])
        marketId = self.safe_string(details, 'symbol')
        market = self.safe_market(marketId, market)
        timestamp = self.parse8601(self.safe_string_2(details, 'timestamp', 'receivedTime'))
        if price is None:
            price = self.safe_string(details, 'limitPrice')
        amount = self.safe_string(details, 'quantity')
        filled = self.safe_string_2(details, 'filledSize', 'filled', '0.0')
        remaining = self.safe_string(details, 'unfilledSize')
        average = None
        filled2 = '0.0'
        if len(trades) > 0:
            vwapSum = '0.0'
            for i in range(0, len(trades)):
                trade = trades[i]
                tradeAmount = self.safe_string(trade, 'amount')
                tradePrice = self.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 is not None) and (not isClosed) and isPrior and 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 is None:
            if isPrior:
                if amount is not None:
                    # remaining amount before execution minus executed amount
                    remaining = Precise.string_sub(amount, filled2)
            else:
                remaining = amount
        # if fetchOpenOrders are parsed
        if (amount is None) and (not isPrior) and (remaining is not None):
            amount = Precise.string_add(filled, remaining)
        cost = None
        if (filled is not None) and (market is not None):
            whichPrice = average if (average is not None) else price
            if whichPrice is not None:
                if market['linear']:
                    cost = Precise.string_mul(filled, whichPrice)  # in quote
                else:
                    cost = Precise.string_div(filled, whichPrice)  # in base
        id = self.safe_string_2(order, 'order_id', 'orderId')
        if id is None:
            id = self.safe_string_2(details, 'orderId', 'uid')
        type = self.safe_string_lower_2(details, 'type', 'orderType')
        timeInForce = 'gtc'
        if type == 'ioc' or self.parse_order_type(type) == 'market':
            timeInForce = 'ioc'
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': self.safe_string_2(details, 'clientOrderId', 'clientId'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'symbol': self.safe_string(market, 'symbol'),
            'type': self.parse_order_type(type),
            'timeInForce': timeInForce,
            'postOnly': type == 'post',
            'side': self.safe_string(details, 'side'),
            'price': price,
            'stopPrice': self.safe_string(details, 'triggerPrice'),
            'triggerPrice': self.safe_string(details, 'triggerPrice'),
            'amount': amount,
            'cost': cost,
            'average': average,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': None,
            'fees': None,
            'trades': trades,
        })

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        response = self.privateGetFills(params)
        #
        #    {
        #        "result": "success",
        #        "serverTime": "2016-02-25T09:45:53.818Z",
        #        "fills": [
        #            {
        #                "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 self.parse_trades(response['fills'], market, since, limit)

    def fetch_balance(self, params={}):
        """
        Fetch the balance for a sub-account, all sub-account balances are inside 'info' in the response
        :param dict params: Exchange specific parameters
        :param str params['type']: The sub-account type to query the balance of, possible values include 'flex', 'cash'/'main'/'funding', or a market symbol * defaults to 'cash' *
        :param str params['symbol']: A unified market symbol, when assigned the balance for a trading market that matches the symbol is returned
        :returns: A `balance structure <https://docs.ccxt.com/en/latest/manual.html#balance-structure>`
        """
        self.load_markets()
        type = self.safe_string_2(params, 'type', 'account')
        symbol = self.safe_string(params, 'symbol')
        params = self.omit(params, ['type', 'account', 'symbol'])
        response = self.privateGetAccounts(params)
        #
        #    {
        #        result: 'success',
        #        accounts: {
        #            fi_xbtusd: {
        #                auxiliary: {usd: '0', pv: '0.0', pnl: '0.0', af: '0.0', funding: '0.0'},
        #                marginRequirements: {im: '0.0', mm: '0.0', lt: '0.0', tt: '0.0'},
        #                triggerEstimates: {im: '0', mm: '0', lt: '0', tt: '0'},
        #                balances: {xbt: '0.0'},
        #                currency: 'xbt',
        #                type: 'marginAccount'
        #            },
        #            cash: {
        #                balances: {
        #                    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: {
        #                auxiliary: {usd: '0', pv: '0.0', pnl: '0.0', af: '0.0', funding: '0.0'},
        #                marginRequirements: {im: '0.0', mm: '0.0', lt: '0.0', tt: '0.0'},
        #                triggerEstimates: {im: '0', mm: '0', lt: '0', tt: '0'},
        #                balances: {xbt: '0.0'},
        #                currency: 'xbt',
        #                type: 'marginAccount'
        #            },
        #            fi_xrpusd: {
        #                auxiliary: {usd: '0', pv: '11.0', pnl: '0.0', af: '11.0', funding: '0.0'},
        #                marginRequirements: {im: '0.0', mm: '0.0', lt: '0.0', tt: '0.0'},
        #                triggerEstimates: {im: '0', mm: '0', lt: '0', tt: '0'},
        #                balances: {xrp: '11.0'},
        #                currency: 'xrp',
        #                type: 'marginAccount'
        #            },
        #            fi_ethusd: {
        #                auxiliary: {usd: '0', pv: '0.0', pnl: '0.0', af: '0.0', funding: '0.0'},
        #                marginRequirements: {im: '0.0', mm: '0.0', lt: '0.0', tt: '0.0'},
        #                triggerEstimates: {im: '0', mm: '0', lt: '0', tt: '0'},
        #                balances: {eth: '0.0'},
        #                currency: 'eth',
        #                type: 'marginAccount'
        #            },
        #            fi_ltcusd: {
        #                auxiliary: {usd: '0', pv: '0.0', pnl: '0.0', af: '0.0', funding: '0.0'},
        #                marginRequirements: {im: '0.0', mm: '0.0', lt: '0.0', tt: '0.0'},
        #                triggerEstimates: {im: '0', mm: '0', lt: '0', tt: '0'},
        #                balances: {ltc: '0.0'},
        #                currency: 'ltc',
        #                type: 'marginAccount'
        #            },
        #            fi_bchusd: {
        #                auxiliary: {usd: '0', pv: '0.0', pnl: '0.0', af: '0.0', funding: '0.0'},
        #                marginRequirements: {im: '0.0', mm: '0.0', lt: '0.0', tt: '0.0'},
        #                triggerEstimates: {im: '0', mm: '0', lt: '0', tt: '0'},
        #                balances: {bch: '0.0'},
        #                currency: 'bch',
        #                type: 'marginAccount'
        #            },
        #            flex: {
        #                currencies: {},
        #                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 = self.safe_string(response, 'serverTime')
        if type == 'marginAccount' or type == 'margin':
            if symbol is None:
                raise ArgumentsRequired(self.id + ' fetchBalance requires symbol argument for margin accounts')
            type = symbol
        if type is None:
            type = 'cash' if (symbol is None) else symbol
        accountName = self.parse_account(type)
        accounts = self.safe_value(response, 'accounts')
        account = self.safe_value(accounts, accountName)
        if account is None:
            type = '' if (type is None) else type
            symbol = '' if (symbol is None) else symbol
            raise BadRequest(self.id + ' fetchBalance has no account for ' + type)
        balance = self.parse_balance(account)
        return self.extend({
            'info': response,
            'timestamp': self.parse8601(datetime),
            'datetime': datetime,
        }, balance)

    def parse_balance(self, response):
        #
        # cashAccount
        #
        #    {
        #        balances: {
        #            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: {
        #            usd: '0',
        #            pv: '11.0',
        #            pnl: '0.0',
        #            af: '11.0',
        #            funding: '0.0'
        #        },
        #        marginRequirements: {im: '0.0', mm: '0.0', lt: '0.0', tt: '0.0'},
        #        triggerEstimates: {im: '0', mm: '0', lt: '0', tt: '0'},
        #        balances: {xrp: '11.0'},
        #        currency: 'xrp',
        #        type: 'marginAccount'
        #    }
        #
        # flex/multiCollateralMarginAccount
        #
        #    {
        #       currencies: {
        #            USDT: {
        #                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 = self.safe_string_2(response, 'accountType', 'type')
        isFlex = (accountType == 'multiCollateralMarginAccount')
        isCash = (accountType == 'cashAccount')
        balances = self.safe_value_2(response, 'balances', 'currencies', {})
        result = {}
        currencyIds = list(balances.keys())
        for i in range(0, len(currencyIds)):
            currencyId = currencyIds[i]
            balance = balances[currencyId]
            code = self.safe_currency_code(currencyId)
            splitCode = code.split('_')
            codeLength = len(splitCode)
            if codeLength > 1:
                continue   # Removes contract codes like PI_XRPUSD
            account = self.account()
            if isFlex:
                account['total'] = self.safe_string(balance, 'quantity')
                account['free'] = self.safe_string(balance, 'available')
            elif isCash:
                account['used'] = '0.0'
                account['total'] = balance
            else:
                auxiliary = self.safe_value(response, 'auxiliary')
                account['free'] = self.safe_string(auxiliary, 'af')
                account['total'] = self.safe_string(auxiliary, 'pv')
            result[code] = account
        return self.safe_balance(result)

    def fetch_funding_rate_history(self, symbol=None, since=None, limit=None, params={}):
        self.check_required_symbol('fetchFundingRateHistory', symbol)
        self.load_markets()
        market = self.market(symbol)
        if not market['swap']:
            raise BadRequest(self.id + ' fetchFundingRateHistory() supports swap contracts only')
        request = {
            'symbol': market['id'].upper(),
        }
        response = self.publicGetHistoricalfundingrates(self.extend(request, params))
        #
        #    {
        #        rates: [
        #          {
        #            timestamp: '2018-08-31T16:00:00.000Z',
        #            fundingRate: '2.18900669884E-7',
        #            relativeFundingRate: '0.000060779960000000'
        #          },
        #          ...
        #        ]
        #    }
        #
        rates = self.safe_value(response, 'rates')
        result = []
        for i in range(0, len(rates)):
            item = rates[i]
            datetime = self.safe_string(item, 'timestamp')
            result.append({
                'info': item,
                'symbol': symbol,
                'fundingRate': self.safe_number(item, 'fundingRate'),
                'timestamp': self.parse8601(datetime),
                'datetime': datetime,
            })
        sorted = self.sort_by(result, 'timestamp')
        return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)

    def fetch_positions(self, symbols=None, params={}):
        """
        Fetches current contract trading positions
        :param [str] symbols: List of unified symbols
        :param dict params: Not used by krakenfutures
        :returns: Parsed exchange response for positions
        """
        self.load_markets()
        request = {}
        response = self.privateGetOpenpositions(request)
        #
        #    {
        #        result: 'success',
        #        openPositions: [
        #            {
        #                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 = self.parse_positions(response)
        return self.filter_by_array(result, 'symbol', symbols, False)

    def parse_positions(self, response, symbols=None, params={}):
        result = []
        positions = self.safe_value(response, 'openPositions')
        for i in range(0, len(positions)):
            position = self.parse_position(positions[i])
            result.append(position)
        return result

    def parse_position(self, position, market=None):
        # 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 = self.safe_number(position, 'maxFixedLeverage')
        marginType = 'cross'
        if leverage is not None:
            marginType = 'isolated'
        datetime = self.safe_string(position, 'fillTime')
        marketId = self.safe_string(position, 'symbol')
        market = self.safe_market(marketId, market)
        return {
            'info': position,
            'symbol': market['symbol'],
            'timestamp': self.parse8601(datetime),
            'datetime': datetime,
            'initialMargin': None,
            'initialMarginPercentage': None,
            'maintenanceMargin': None,
            'maintenanceMarginPercentage': None,
            'entryPrice': self.safe_number(position, 'price'),
            'notional': None,
            'leverage': leverage,
            'unrealizedPnl': self.safe_number(position, 'unrealizedFunding'),
            'contracts': self.safe_number(position, 'size'),
            'contractSize': self.safe_number(market, 'contractSize'),
            'marginRatio': None,
            'liquidationPrice': None,
            'markPrice': None,
            'collateral': None,
            'marginType': marginType,
            'side': self.safe_string(position, 'side'),
            'percentage': None,
        }

    def fetch_leverage_tiers(self, symbols=None, params={}):
        self.load_markets()
        response = self.publicGetInstruments(params)
        #
        #    {
        #        "result": "success",
        #        "instruments": [
        #            {
        #                "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": [
        #                    {
        #                        "contracts":0,
        #                        "initialMargin":0.02,
        #                        "maintenanceMargin":0.01
        #                    },
        #                    {
        #                        "contracts":250000,
        #                        "initialMargin":0.04,
        #                        "maintenanceMargin":0.02
        #                    },
        #                    ...
        #                ],
        #                "isin": "GB00JVMLMP88",
        #                "retailMarginLevels": [
        #                    {
        #                        "contracts": 0,
        #                        "initialMargin": 0.5,
        #                        "maintenanceMargin": 0.25
        #                    }
        #                ],
        #                "tags": [],
        #            },
        #            {
        #                "symbol": "in_xbtusd",
        #                "type": "spot index",
        #                "tradeable":false
        #            }
        #        ]
        #        "serverTime": "2018-07-19T11:32:39.433Z"
        #    }
        #
        data = self.safe_value(response, 'instruments')
        return self.parse_leverage_tiers(data, symbols, 'symbol')

    def parse_market_leverage_tiers(self, info, market=None):
        """
         * @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": [
        #            {
        #                "contracts":0,
        #                "initialMargin":0.02,
        #                "maintenanceMargin":0.01
        #            },
        #            {
        #                "contracts":250000,
        #                "initialMargin":0.04,
        #                "maintenanceMargin":0.02
        #            },
        #            ...
        #        ],
        #        "isin": "GB00JVMLMP88",
        #        "retailMarginLevels": [
        #            {
        #                "contracts": 0,
        #                "initialMargin": 0.5,
        #                "maintenanceMargin": 0.25
        #            }
        #        ],
        #        "tags": [],
        #    }
        #
        marginLevels = self.safe_value(info, 'marginLevels')
        id = self.safe_string(info, 'symbol')
        market = self.safe_market(id, market)
        tiers = []
        for i in range(0, len(marginLevels)):
            tier = marginLevels[i]
            initialMargin = self.safe_string(tier, 'initialMargin')
            notionalFloor = self.safe_number(tier, 'contracts')
            if i != 0:
                tiersLength = len(tiers)
                previousTier = tiers[tiersLength - 1]
                previousTier['notionalCap'] = notionalFloor
            tiers.append({
                'tier': self.sum(i, 1),
                'currency': market['quote'],
                'notionalFloor': notionalFloor,
                'notionalCap': None,
                'maintenanceMarginRate': self.safe_number(tier, 'maintenanceMargin'),
                'maxLeverage': self.parse_number(Precise.string_div('1', initialMargin)),
                'info': tier,
            })
        return tiers

    def parse_transfer(self, transfer, currency=None):
        #
        # transfer
        #
        #    {
        #        result: 'success',
        #        serverTime: '2022-04-12T01:22:53.420Z'
        #    }
        #
        datetime = self.safe_string(transfer, 'serverTime')
        return {
            'info': transfer,
            'id': None,
            'timestamp': self.parse8601(datetime),
            'datetime': datetime,
            'currency': self.safe_string(currency, 'code'),
            'amount': None,
            'fromAccount': None,
            'toAccount': None,
            'status': self.safe_string(transfer, 'result'),
        }

    def parse_account(self, account):
        accountByType = {
            'main': 'cash',
            'funding': 'cash',
            'future': 'cash',
            'futures': 'cash',
            'cashAccount': 'cash',
            'multiCollateralMarginAccount': 'flex',
            'multiCollateral': 'flex',
            'multiCollateralMargin': 'flex',
        }
        if account in accountByType:
            return accountByType[account]
        elif account in self.markets:
            market = self.market(account)
            marketId = market['id']
            splitId = marketId.split('_')
            if market['inverse']:
                return 'fi_' + self.safe_string(splitId, 1)
            else:
                return 'fv_' + self.safe_string(splitId, 1)
        else:
            return account

    def transfer_out(self, 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
        :returns: a `transfer structure <https://docs.ccxt.com/en/latest/manual.html#transfer-structure>`
        """
        return self.transfer(code, amount, 'future', 'spot', params)

    def transfer(self, code, amount, fromAccount, toAccount, params={}):
        """
        transfers currencies between sub-accounts
        :param str code: Unified currency code
        :param float amount: Size of the transfer
        :param str fromAccount: 'main'/'funding'/'future', 'flex', or a unified market symbol
        :param str toAccount: 'main'/'funding', 'flex', 'spot' or a unified market symbol
        :param dict params: Exchange specific parameters
        :returns: a `transfer structure <https://docs.ccxt.com/en/latest/manual.html#transfer-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        method = 'privatePostTransfer'
        request = {
            'amount': self.currency_to_precision(code, amount),
        }
        if fromAccount == 'spot':
            raise BadRequest(self.id + ' transfer does not yet support transfers from spot')
        if toAccount == 'spot':
            if self.parse_account(fromAccount) != 'cash':
                raise BadRequest(self.id + ' transfer cannot transfer from ' + fromAccount + ' to ' + toAccount)
            method = 'privatePostWithdrawal'
            request['currency'] = currency['id']
        else:
            request['fromAccount'] = self.parse_account(fromAccount)
            request['toAccount'] = self.parse_account(toAccount)
            request['unit'] = currency['id']
        response = getattr(self, method)(self.extend(request, params))
        #
        #    {
        #        result: 'success',
        #        serverTime: '2022-04-12T01:22:53.420Z'
        #    }
        #
        transfer = self.parse_transfer(response, currency)
        return self.extend(transfer, {
            'amount': amount,
            'fromAccount': fromAccount,
            'toAccount': toAccount,
        })

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if response is None:
            return
        if code == 429:
            raise DDoSProtection(self.id + ' ' + body)
        message = self.safe_string(response, 'error')
        if message is None:
            return
        feedback = self.id + ' ' + body
        self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
        self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
        if code == 400:
            raise BadRequest(feedback)
        raise ExchangeError(feedback)  # unknown message

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        apiVersions = self.safe_value(self.options['versions'], api, {})
        methodVersions = self.safe_value(apiVersions, method, {})
        defaultVersion = self.safe_string(methodVersions, path, self.version)
        version = self.safe_string(params, 'version', defaultVersion)
        params = self.omit(params, 'version')
        apiAccess = self.safe_value(self.options['access'], api, {})
        methodAccess = self.safe_value(apiAccess, method, {})
        access = self.safe_string(methodAccess, path, 'public')
        endpoint = version + '/' + self.implode_params(path, params)
        params = self.omit(params, self.extract_params(path))
        query = endpoint
        postData = ''
        if params:
            postData = self.urlencode(params)
            query += '?' + postData
        url = self.urls['api'][api] + query
        if api == 'private' or access == 'private':
            auth = postData + '/api/' + endpoint  # 1
            hash = self.hash(self.encode(auth), 'sha256', 'binary')  # 2
            secret = self.base64_to_binary(self.secret)  # 3
            signature = self.hmac(hash, secret, hashlib.sha512, 'base64')  # 4-5
            headers = {
                'Content-Type': 'application/json',
                'APIKey': self.apiKey,
                'Authent': signature,
            }
        return {'url': url, 'method': method, 'body': body, 'headers': headers}
