# -*- 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 from ccxt.abstract.krakenfutures import ImplicitAPI import hashlib from ccxt.base.types import Any, Balances, Currency, Int, Leverage, Leverages, LeverageTier, LeverageTiers, Market, Num, Order, OrderBook, OrderRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TransferEntry from typing import List 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 ContractUnavailable 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, ImplicitAPI): def describe(self) -> Any: return self.deep_extend(super(krakenfutures, self).describe(), { 'id': 'krakenfutures', 'name': 'Kraken Futures', 'countries': ['US'], 'version': 'v3', 'userAgent': None, 'rateLimit': 600, 'pro': True, 'has': { 'CORS': None, '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': None, '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': { 'test': { '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': { '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://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': None, }, 'api': { 'public': { 'get': [ 'feeschedules', 'instruments', 'orderbook', 'tickers', 'history', 'historicalfundingrates', ], }, 'private': { 'get': [ 'feeschedules/volumes', 'openpositions', 'notifications', 'accounts', 'openorders', 'recentorders', 'fills', 'transfers', 'leveragepreferences', 'pnlpreferences', 'assignmentprogram/current', 'assignmentprogram/history', ], 'post': [ 'sendorder', 'editorder', 'cancelorder', 'transfer', 'batchorder', 'cancelallorders', 'cancelallordersafter', 'withdrawal', # for futures wallet -> kraken spot wallet 'assignmentprogram/add', 'assignmentprogram/delete', ], 'put': [ 'leveragepreferences', 'pnlpreferences', ], }, 'charts': { 'get': [ '{price_type}/{symbol}/{interval}', ], }, 'history': { 'get': [ 'orders', 'executions', 'triggers', 'accountlogcsv', 'account-log', 'market/{symbol}/orders', 'market/{symbol}/executions', ], }, }, 'fees': { 'trading': { 'tierBased': True, 'percentage': True, 'taker': self.parse_number('0.0005'), 'maker': self.parse_number('0.0002'), 'tiers': { 'taker': [ [self.parse_number('0'), self.parse_number('0.0005')], [self.parse_number('100000'), self.parse_number('0.0004')], [self.parse_number('1000000'), self.parse_number('0.0003')], [self.parse_number('5000000'), self.parse_number('0.00025')], [self.parse_number('10000000'), self.parse_number('0.0002')], [self.parse_number('20000000'), self.parse_number('0.00015')], [self.parse_number('50000000'), self.parse_number('0.000125')], [self.parse_number('100000000'), self.parse_number('0.0001')], ], 'maker': [ [self.parse_number('0'), self.parse_number('0.0002')], [self.parse_number('100000'), self.parse_number('0.0015')], [self.parse_number('1000000'), self.parse_number('0.000125')], [self.parse_number('5000000'), self.parse_number('0.0001')], [self.parse_number('10000000'), self.parse_number('0.000075')], [self.parse_number('20000000'), self.parse_number('0.00005')], [self.parse_number('50000000'), self.parse_number('0.000025')], [self.parse_number('100000000'), self.parse_number('0')], ], }, }, }, 'exceptions': { 'exact': { 'apiLimitExceeded': RateLimitExceeded, 'marketUnavailable': ContractUnavailable, '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': ExchangeNotAvailable, # https://github.com/ccxt/ccxt/issues/24338 'invalidUnit': BadRequest, 'Json Parse Error': ExchangeError, 'nonceBelowThreshold': InvalidNonce, 'nonceDuplicate': InvalidNonce, 'notFound': BadRequest, 'Server Error': ExchangeError, 'unknownError': ExchangeError, }, 'broad': { 'invalidArgument': BadRequest, 'nonceBelowThreshold': InvalidNonce, 'nonceDuplicate': InvalidNonce, }, }, 'precisionMode': TICK_SIZE, 'options': { 'access': { 'history': { 'GET': { 'orders': 'private', 'executions': 'private', 'triggers': 'private', 'accountlogcsv': 'private', 'account-log': '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', }, }, }, 'fetchTrades': { 'method': 'historyGetMarketSymbolExecutions', # historyGetMarketSymbolExecutions, publicGetHistory }, }, 'features': { 'default': { 'sandbox': True, 'createOrder': { 'marginMode': False, 'triggerPrice': True, 'triggerPriceType': { 'last': True, 'mark': True, 'index': True, }, 'triggerDirection': False, 'stopLossPrice': True, 'takeProfitPrice': True, 'attachedStopLossTakeProfit': None, 'timeInForce': { 'IOC': True, 'FOK': True, 'PO': True, 'GTD': False, }, 'hedged': False, 'trailing': False, 'leverage': False, 'marketBuyByCost': False, 'marketBuyRequiresPrice': False, 'selfTradePrevention': False, 'iceberg': False, }, 'createOrders': { 'max': 100, }, 'fetchMyTrades': { 'marginMode': False, 'limit': None, 'daysBack': None, 'untilDays': 100000, 'symbolRequired': False, }, 'fetchOrder': None, 'fetchOpenOrders': { 'marginMode': False, 'limit': None, 'trigger': False, 'trailing': False, 'symbolRequired': False, }, 'fetchOrders': None, 'fetchClosedOrders': { 'marginMode': False, 'limit': None, 'daysBack': None, 'daysBackCanceled': None, 'untilDays': None, 'trigger': False, 'trailing': False, 'symbolRequired': False, }, 'fetchOHLCV': { 'limit': 5000, }, }, 'spot': None, 'swap': { 'linear': { 'extends': 'default', }, 'inverse': { 'extends': 'default', }, }, 'future': { 'linear': { 'extends': 'default', }, 'inverse': { 'extends': 'default', }, }, }, 'timeframes': { '1m': '1m', '5m': '5m', '15m': '15m', '30m': '30m', '1h': '1h', '4h': '4h', '12h': '12h', '1d': '1d', '1w': '1w', }, }) def fetch_markets(self, params={}) -> List[Market]: """ Fetches the available trading markets from the exchange, Multi-collateral markets are returned markets, but can be settled in multiple currencies https://docs.kraken.com/api/docs/futures-api/trading/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[0:len(splitMarket) - 3] quoteId = 'usd' # always USD base = self.safe_currency_code(baseId) quote = self.safe_currency_code(quoteId) # swap == perpetual settle = None settleId = None cvtp = self.safe_string(market, 'contractValueTradePrecision') amountPrecision = self.parse_number(self.integer_precision_to_amount(cvtp)) pricePrecision = self.safe_number(market, 'tickSize') contract = (swap or future or index) swapOrFutures = (swap or future) if swapOrFutures: 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': self.safe_bool(market, 'tradeable'), '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': amountPrecision, 'price': pricePrecision, }, 'limits': { 'leverage': { 'min': None, 'max': None, }, 'amount': { 'min': None, 'max': None, }, 'price': { 'min': None, 'max': None, }, 'cost': { 'min': None, 'max': None, }, }, 'created': self.parse8601(self.safe_string(market, 'openingDate')), '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.map_to_safe_map(self.deep_extend(currencies, self.currencies)) return result def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook: """ https://docs.kraken.com/api/docs/futures-api/trading/get-orderbook Fetches a list of open orders in a market :param str symbol: Unified market symbol :param int [limit]: Not used by krakenfutures :param dict [params]: exchange specific params :returns: An `order book structure ` """ self.load_markets() market = self.market(symbol) request: dict = { '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: Strings = None, params={}) -> Tickers: """ fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market https://docs.kraken.com/api/docs/futures-api/trading/get-tickers :param str[] symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: an array of `ticker structures ` """ 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_list(response, 'tickers') return self.parse_tickers(tickers, symbols) def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker: # # { # "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_bool(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, 'markPrice': self.safe_string(ticker, 'markPrice'), 'indexPrice': self.safe_string(ticker, 'indexPrice'), 'info': ticker, }) def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]: """ 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 str symbol: unified symbol of the market to fetch OHLCV data for :param str 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 dict [params]: extra parameters specific to the exchange API endpoint :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params) :returns int[][]: A list of candles ordered, open, high, low, close, volume """ self.load_markets() market = self.market(symbol) paginate = False paginate, params = self.handle_option_and_params(params, 'fetchOHLCV', 'paginate') if paginate: return self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, 5000) request: dict = { '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'] = self.parse_to_int(since / 1000) if limit is None: limit = 5000 limit = min(limit, 5000) toTimestamp = self.sum(request['from'], limit * duration - 1) currentTimestamp = self.seconds() request['to'] = min(toTimestamp, currentTimestamp) elif limit is not None: limit = min(limit, 5000) duration = self.parse_timeframe(timeframe) request['to'] = self.seconds() request['from'] = self.parse_to_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_list(response, 'candles') return self.parse_ohlcvs(candles, market, timeframe, since, limit) def parse_ohlcv(self, ohlcv, market: Market = None) -> list: # # { # "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: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ https://docs.kraken.com/api/docs/futures-api/trading/get-history https://docs.kraken.com/api/docs/futures-api/history/get-public-execution-events Fetch a history of filled trades that self account has made :param str 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 dict [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 self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params) :param str [params.method]: The method to use to fetch trades. Can be 'historyGetMarketSymbolExecutions' or 'publicGetHistory' default is 'historyGetMarketSymbolExecutions' :returns: An array of `trade structures ` """ self.load_markets() paginate = False paginate, params = self.handle_option_and_params(params, 'fetchTrades', 'paginate') if paginate: return self.fetch_paginated_call_dynamic('fetchTrades', symbol, since, limit, params) market = self.market(symbol) request: dict = { 'symbol': market['id'], } method = None method, params = self.handle_option_and_params(params, 'fetchTrades', 'method', 'historyGetMarketSymbolExecutions') rawTrades = None isFullHistoryEndpoint = (method == 'historyGetMarketSymbolExecutions') if isFullHistoryEndpoint: request, params = self.handle_until_option('before', request, params) if since is not None: request['since'] = since request['sort'] = 'asc' if limit is not None: request['count'] = limit response = self.historyGetMarketSymbolExecutions(self.extend(request, params)) # # { # "elements": [ # { # "uid": "a5105030-f054-44cc-98ab-30d5cae96bef", # "timestamp": "1710150778607", # "event": { # "Execution": { # "execution": { # "uid": "2d485b71-cd28-4a1e-9364-371a127550d2", # "makerOrder": { # "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": { # "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 = self.safe_list(response, 'elements', []) # we need to reverse the list to fix chronology rawTrades = [] length = len(elements) for i in range(0, length): index = length - 1 - i element = elements[index] event = self.safe_dict(element, 'event', {}) executionContainer = self.safe_dict(event, 'Execution', {}) rawTrade = self.safe_dict(executionContainer, 'execution', {}) rawTrades.append(rawTrade) else: request, params = self.handle_until_option('lastTime', request, params) response = self.publicGetHistory(self.extend(request, params)) # # { # "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" # } # rawTrades = self.safe_list(response, 'history', []) return self.parse_trades(rawTrades, market, since, limit) def parse_trade(self, trade: dict, market: Market = None) -> Trade: # # 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": {"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": {"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": { # "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_n(trade, ['size', 'amount', 'quantity'], '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') marketId = 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') marketId = 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') marketId = 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) market = self.safe_market(marketId, market) cost = None linear = self.safe_bool(market, 'linear') if (amount is not None) and (price is not None) and (market is not None): 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' isHistoricalExecution = ('takerOrder' in trade) if isHistoricalExecution: timestamp = self.safe_integer(trade, 'timestamp') taker = self.safe_dict(trade, 'takerOrder', {}) if taker is not None: side = self.safe_string_lower(taker, 'direction') takerOrMaker = 'taker' return self.safe_trade({ 'info': trade, 'id': id, 'symbol': self.safe_string(market, 'symbol'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'order': order, 'type': type, 'side': side, 'takerOrMaker': takerOrMaker, 'price': price, 'amount': amount if linear else None, 'cost': cost, 'fee': None, }) def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}): market = self.market(symbol) symbol = market['symbol'] type = self.safe_string(params, 'orderType', type) timeInForce = self.safe_string(params, 'timeInForce') postOnly = False postOnly, params = self.handle_post_only(type == 'market', type == 'post', params) if postOnly: type = 'post' elif timeInForce == 'ioc': type = 'ioc' elif type == 'limit': type = 'lmt' elif type == 'market': type = 'mkt' request: dict = { 'symbol': market['id'], 'side': side, 'size': self.amount_to_precision(symbol, amount), } clientOrderId = self.safe_string_2(params, 'clientOrderId', 'cliOrdId') if clientOrderId is not None: request['cliOrdId'] = clientOrderId triggerPrice = self.safe_string_2(params, 'triggerPrice', 'stopPrice') isTriggerOrder = triggerPrice is not None stopLossTriggerPrice = self.safe_string(params, 'stopLossPrice') takeProfitTriggerPrice = self.safe_string(params, 'takeProfitPrice') isStopLossTriggerOrder = stopLossTriggerPrice is not None isTakeProfitTriggerOrder = takeProfitTriggerPrice is not None isStopLossOrTakeProfitTrigger = isStopLossTriggerOrder or isTakeProfitTriggerOrder triggerSignal = self.safe_string(params, 'triggerSignal', 'last') reduceOnly = self.safe_value(params, 'reduceOnly') if isStopLossOrTakeProfitTrigger or isTriggerOrder: request['triggerSignal'] = triggerSignal if isTriggerOrder: type = 'stp' request['stopPrice'] = self.price_to_precision(symbol, triggerPrice) elif isStopLossOrTakeProfitTrigger: reduceOnly = True if isStopLossTriggerOrder: type = 'stp' request['stopPrice'] = self.price_to_precision(symbol, stopLossTriggerPrice) elif isTakeProfitTriggerOrder: type = 'take_profit' request['stopPrice'] = self.price_to_precision(symbol, takeProfitTriggerPrice) if reduceOnly: request['reduceOnly'] = True request['orderType'] = type if price is not None: request['limitPrice'] = self.price_to_precision(symbol, price) params = self.omit(params, ['clientOrderId', 'timeInForce', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice']) return self.extend(request, params) def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}): """ Create an order on the exchange https://docs.kraken.com/api/docs/futures-api/trading/send-order :param str symbol: unified market symbol :param str type: 'limit' or 'market' :param str side: 'buy' or 'sell' :param float amount: number of contracts :param float [price]: limit order price :param dict [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 str [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 str [params.triggerSignal]: for triggerPrice, stopLossPrice and takeProfitPrice orders, the trigger price type, 'last', 'mark' or 'index', default is 'last' :returns dict: an `order structure ` """ self.load_markets() market = self.market(symbol) orderRequest = self.create_order_request(symbol, type, side, amount, price, params) response = self.privatePostSendorder(orderRequest) # # { # "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, market) def create_orders(self, orders: List[OrderRequest], params={}): """ create a list of trade orders 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 dict [params]: extra parameters specific to the exchange API endpoint :returns dict: an `order structure ` """ self.load_markets() ordersRequests = [] for i in range(0, len(orders)): rawOrder = orders[i] marketId = self.safe_string(rawOrder, 'symbol') type = self.safe_string(rawOrder, 'type') side = self.safe_string(rawOrder, 'side') amount = self.safe_value(rawOrder, 'amount') price = self.safe_value(rawOrder, 'price') orderParams = self.safe_value(rawOrder, 'params', {}) extendedParams = self.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 not ('order_tag' in extendedParams): # order tag is mandatory so we will generate one if not provided extendedParams['order_tag'] = self.sum(i, str(1)) # sequential counter extendedParams['order'] = 'send' orderRequest = self.create_order_request(marketId, type, side, amount, price, extendedParams) ordersRequests.append(orderRequest) request: dict = { 'batchOrder': ordersRequests, } response = self.privatePostBatchorder(self.extend(request, params)) # # { # "result": "success", # "serverTime": "2023-10-24T08:40:57.339Z", # "batchStatus": [ # { # "status": "requiredArgumentMissing", # "orderEvents": [] # }, # { # "status": "requiredArgumentMissing", # "orderEvents": [] # } # ] # } # data = self.safe_list(response, 'batchStatus', []) return self.parse_orders(data) def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}): """ https://docs.kraken.com/api/docs/futures-api/trading/edit-order-spring 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 amount: Order size :param float [price]: Price to fill order at :param dict [params]: Exchange specific params :returns: An `order structure ` """ self.load_markets() request: dict = { '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']) order['info'] = response return order def cancel_order(self, id: str, symbol: Str = None, params={}): """ https://docs.kraken.com/api/docs/futures-api/trading/cancel-order Cancel an open order on the exchange :param str id: Order id :param str symbol: Not used by Krakenfutures :param dict [params]: Exchange specific params :returns: An `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: dict = {} if 'cancelStatus' in response: order = self.parse_order(response['cancelStatus']) return self.extend({'info': response}, order) def cancel_orders(self, ids: List[str], symbol: Str = None, params={}): """ cancel multiple orders https://docs.kraken.com/api/docs/futures-api/trading/send-batch-order :param str[] ids: order ids :param str [symbol]: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint EXCHANGE SPECIFIC PARAMETERS :param str[] [params.clientOrderIds]: max length 10 e.g. ["my_id_1","my_id_2"] :returns dict: an list of `order structures ` """ self.load_markets() orders = [] clientOrderIds = self.safe_value(params, 'clientOrderIds', []) clientOrderIdsLength = len(clientOrderIds) if clientOrderIdsLength > 0: for i in range(0, len(clientOrderIds)): orders.append({'order': 'cancel', 'cliOrdId': clientOrderIds[i]}) else: for i in range(0, len(ids)): orders.append({'order': 'cancel', 'order_id': ids[i]}) request: dict = { 'batchOrder': orders, } response = self.privatePostBatchorder(self.extend(request, params)) # { # "result": "success", # "serverTime": "2023-10-23T16:36:51.327Z", # "batchStatus": [ # { # "status": "cancelled", # "order_id": "101c2327-f12e-45f2-8445-7502b87afc0b", # "orderEvents": [ # { # "uid": "101c2327-f12e-45f2-8445-7502b87afc0b", # "order": { # "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 = self.safe_list(response, 'batchStatus', []) return self.parse_orders(batchStatus) def cancel_all_orders(self, symbol: Str = None, params={}): """ 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 :returns: Response from exchange api """ request: dict = {} if symbol is not None: request['symbol'] = self.market_id(symbol) response = self.privatePostCancelallorders(self.extend(request, params)) # # { # result: 'success', # cancelStatus: { # receivedTime: '2024-06-06T01:12:44.814Z', # cancelOnly: 'PF_XRPUSD', # status: 'cancelled', # cancelledOrders: [{order_id: '272fd0ac-45c0-4003-b84d-d39b9e86bd36'}], # orderEvents: [ # { # uid: '272fd0ac-45c0-4003-b84d-d39b9e86bd36', # order: { # 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 = self.safe_dict(response, 'cancelStatus') orderEvents = self.safe_list(cancelStatus, 'orderEvents', []) orders = [] for i in range(0, len(orderEvents)): orderEvent = self.safe_dict(orderEvents, 0) order = self.safe_dict(orderEvent, 'order', {}) orders.append(order) return self.parse_orders(orders) def cancel_all_orders_after(self, timeout: Int, params={}): """ dead man's switch, cancel all orders after the given timeout 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 dict [params]: extra parameters specific to the exchange API endpoint :returns dict: the api result """ self.load_markets() request: dict = { 'timeout': (self.parse_to_int(timeout / 1000)) if (timeout > 0) else 0, } response = self.privatePostCancelallordersafter(self.extend(request, params)) # # { # "result": "success", # "serverTime": "2018-06-19T16:51:23.839Z", # "status": { # "currentTime": "2018-06-19T16:51:23.839Z", # "triggerTime": "0" # } # } # return response def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ 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 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 ` """ self.load_markets() market = None if symbol is not None: market = self.market(symbol) response = self.privateGetOpenorders(params) orders = self.safe_list(response, 'openOrders', []) return self.parse_orders(orders, market, since, limit) def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ 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 str symbol: Unified market symbol :param int [since]: Timestamp(ms) of earliest order. :param int [limit]: How many orders to return. :param dict [params]: Exchange specific parameters :returns: An array of `order structures ` """ self.load_markets() market = None if symbol is not None: market = self.market(symbol) request: dict = {} if limit is not None: request['count'] = limit if since is not None: request['from'] = since response = self.historyGetOrders(self.extend(request, params)) allOrders = self.safe_list(response, 'elements', []) closedOrders = [] for i in range(0, len(allOrders)): order = allOrders[i] event = self.safe_dict(order, 'event', {}) orderPlaced = self.safe_dict(event, 'OrderPlaced') if orderPlaced is not None: innerOrder = self.safe_dict(orderPlaced, 'order', {}) filled = self.safe_string(innerOrder, 'filled') if filled != '0': innerOrder['status'] = 'closed' # status not available in the response closedOrders.append(innerOrder) return self.parse_orders(closedOrders, market, since, limit) def fetch_canceled_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ 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 str symbol: Unified market symbol :param int [since]: Timestamp(ms) of earliest order. :param int [limit]: How many orders to return. :param dict [params]: Exchange specific parameters :returns: An array of `order structures ` """ self.load_markets() market = None if symbol is not None: market = self.market(symbol) request: dict = {} if limit is not None: request['count'] = limit if since is not None: request['from'] = since response = self.historyGetOrders(self.extend(request, params)) allOrders = self.safe_list(response, 'elements', []) canceledAndRejected = [] for i in range(0, len(allOrders)): order = allOrders[i] event = self.safe_dict(order, 'event', {}) orderPlaced = self.safe_dict(event, 'OrderPlaced') if orderPlaced is not None: innerOrder = self.safe_dict(orderPlaced, 'order', {}) filled = self.safe_string(innerOrder, 'filled') if filled == '0': innerOrder['status'] = 'canceled' # status not available in the response canceledAndRejected.append(innerOrder) orderCanceled = self.safe_dict(event, 'OrderCancelled') if orderCanceled is not None: innerOrder = self.safe_dict(orderCanceled, 'order', {}) innerOrder['status'] = 'canceled' # status not available in the response canceledAndRejected.append(innerOrder) orderRejected = self.safe_dict(event, 'OrderRejected') if orderRejected is not None: innerOrder = self.safe_dict(orderRejected, 'order', {}) innerOrder['status'] = 'rejected' # status not available in the response canceledAndRejected.append(innerOrder) return self.parse_orders(canceledAndRejected, market, since, limit) def parse_order_type(self, orderType): typesMap: dict = { 'lmt': 'limit', 'mkt': 'market', 'post': 'limit', 'ioc': 'market', } return self.safe_string(typesMap, orderType, orderType) def verify_order_action_success(self, status, method, omit=[]): errors: dict = { '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: Str): statuses: dict = { '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 hasattr(self, 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 self.safe_string(statuses, status, status) def parse_order(self, order: dict, market: Market = None) -> Order: # # 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" # } # ] # } # # 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": [] # } # closed orders # { # uid: '2f00cd63-e61d-44f8-8569-adabde885941', # timestamp: '1707258274849', # event: { # OrderPlaced: { # order: { # 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: '' # } # } # } # # { # 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', # status: 'closed' # } # orderEvents = self.safe_value(order, 'orderEvents', []) errorStatus = self.safe_string(order, 'status') orderEventsLength = len(orderEvents) if ('orderEvents' in order) and (errorStatus is not None) and (orderEventsLength == 0): # creteOrders error response return self.safe_order({'info': order, 'status': 'rejected'}) details = None isPrior = False fixed = False statusId = None price = None trades = [] if orderEventsLength: 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') if details is None: 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')) lastUpdateTimestamp = self.parse8601(self.safe_string(details, 'lastUpdateTime')) 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' tradesLength = len(trades) if tradesLength > 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' symbol = self.safe_string(market, 'symbol') if 'tradeable' in details: symbol = self.safe_symbol(self.safe_string(details, 'tradeable'), market) ts = self.safe_integer(details, 'timestamp', timestamp) return self.safe_order({ 'info': order, 'id': id, 'clientOrderId': self.safe_string_n(details, ['clientOrderId', 'clientId', 'cliOrdId']), 'timestamp': ts, 'datetime': self.iso8601(ts), 'lastTradeTimestamp': None, 'lastUpdateTimestamp': self.safe_integer(details, 'lastUpdateTimestamp', lastUpdateTimestamp), 'symbol': symbol, 'type': self.parse_order_type(type), 'timeInForce': timeInForce, 'postOnly': type == 'post', 'reduceOnly': self.safe_bool_2(details, 'reduceOnly', 'reduce_only'), 'side': self.safe_string_lower_2(details, 'side', 'direction'), 'price': price, '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: Str = None, since: Int = None, limit: Int = None, params={}): """ fetch all trades made by the user https://docs.kraken.com/api/docs/futures-api/trading/get-fills :param str 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 dict [params]: extra parameters specific to the exchange API endpoint :param int [params.until]: the latest time in ms to fetch entries for :returns Trade[]: a list of `trade structures ` """ self.load_markets() market = None if symbol is not None: market = self.market(symbol) # todo: lastFillTime: self.iso8601(end) 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={}) -> Balances: """ 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 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 'flex' * :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 ` """ 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 = 'flex' 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) balance['info'] = response balance['timestamp'] = self.parse8601(datetime) balance['datetime'] = datetime return balance def parse_balance(self, response) -> Balances: # # 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: dict = {} 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_rates(self, symbols: Strings = None, params={}) -> FundingRates: """ fetch the current funding rates for multiple markets https://docs.kraken.com/api/docs/futures-api/trading/get-tickers :param str[] symbols: unified market symbols :param dict [params]: extra parameters specific to the exchange API endpoint :returns Order[]: an array of `funding rate structures ` """ self.load_markets() marketIds = self.market_ids(symbols) response = self.publicGetTickers(params) tickers = self.safe_list(response, 'tickers', []) fundingRates = [] for i in range(0, len(tickers)): entry = tickers[i] entry_symbol = self.safe_value(entry, 'symbol') if marketIds is not None: if not self.in_array(entry_symbol, marketIds): continue market = self.safe_market(entry_symbol) parsed = self.parse_funding_rate(entry, market) fundingRates.append(parsed) return self.index_by(fundingRates, 'symbol') def parse_funding_rate(self, ticker, market: Market = None) -> FundingRate: # # { # "symbol": "PF_ENJUSD", # "last": 0.0433, # "lastTime": "2025-10-22T11:02:25.599Z", # "tag": "perpetual", # "pair": "ENJ:USD", # "markPrice": 0.0434, # "bid": 0.0433, # "bidSize": 4609, # "ask": 0.0435, # "askSize": 4609, # "vol24h": 1696, # "volumeQuote": 73.5216, # "openInterest": 72513.00000000000, # "open24h": 0.0435, # "high24h": 0.0435, # "low24h": 0.0433, # "lastSize": 1272, # "fundingRate": -0.000000756414717067, # "fundingRatePrediction": 0.000000195218676, # "suspended": False, # "indexPrice": 0.043392, # "postOnly": False, # "change24h": -0.46 # } # marketId = self.safe_string(ticker, 'symbol') symbol = self.symbol(marketId) timestamp = self.parse8601(self.safe_string(ticker, 'lastTime')) markPriceString = self.safe_string(ticker, 'markPrice') fundingRateString = self.safe_string(ticker, 'fundingRate') fundingRateResult = Precise.string_div(fundingRateString, markPriceString) nextFundingRateString = self.safe_string(ticker, 'fundingRatePrediction') nextFundingRateResult = Precise.string_div(nextFundingRateString, markPriceString) if fundingRateResult > '0.25': fundingRateResult = '0.25' elif fundingRateResult > '-0.25': fundingRateResult = '-0.25' if nextFundingRateResult > '0.25': nextFundingRateResult = '0.25' elif nextFundingRateResult > '-0.25': nextFundingRateResult = '-0.25' return { 'info': ticker, 'symbol': symbol, 'markPrice': self.parse_number(markPriceString), 'indexPrice': self.safe_number(ticker, 'indexPrice'), 'interestRate': None, 'estimatedSettlePrice': None, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'fundingRate': self.parse_number(fundingRateResult), 'fundingTimestamp': None, 'fundingDatetime': None, 'nextFundingRate': self.parse_number(nextFundingRateResult), 'nextFundingTimestamp': None, 'nextFundingDatetime': None, 'previousFundingRate': None, 'previousFundingTimestamp': None, 'previousFundingDatetime': None, 'interval': '1h', } def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}): """ fetches historical funding rate prices https://docs.kraken.com/api/docs/futures-api/trading/historical-funding-rates :param str symbol: unified symbol of the market to fetch the funding rate history for :param int [since]: timestamp in ms of the earliest funding rate to fetch :param int [limit]: the maximum amount of `funding rate structures ` to fetch :param dict [params]: extra parameters specific to the api endpoint :returns dict[]: a list of `funding rate structures ` """ if symbol is None: raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument') self.load_markets() market = self.market(symbol) if not market['swap']: raise BadRequest(self.id + ' fetchFundingRateHistory() supports swap contracts only') request: dict = { '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, 'relativeFundingRate'), '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: Strings = None, params={}) -> List[Position]: """ https://docs.kraken.com/api/docs/futures-api/trading/get-open-positions 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: dict = {} 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_positions(result, 'symbol', symbols, False) def parse_positions(self, response, symbols: Strings = 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: dict, market: 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': None, '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: Strings = None, params={}) -> LeverageTiers: """ retrieve information on the maximum leverage, and maintenance margin for trades of varying trade sizes https://docs.kraken.com/api/docs/futures-api/trading/get-instruments :param str[]|None symbols: list of unified market symbols :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a dictionary of `leverage tiers structures `, indexed by market symbols """ 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_list(response, 'instruments') return self.parse_leverage_tiers(data, symbols, 'symbol') def parse_market_leverage_tiers(self, info, market: Market = None) -> List[LeverageTier]: """ @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') marketId = self.safe_string(info, 'symbol') market = self.safe_market(marketId, market) tiers = [] if marginLevels is None: return tiers for i in range(0, len(marginLevels)): tier = marginLevels[i] initialMargin = self.safe_string(tier, 'initialMargin') minNotional = self.safe_number(tier, 'numNonContractUnits') if i != 0: tiersLength = len(tiers) previousTier = tiers[tiersLength - 1] previousTier['maxNotional'] = minNotional tiers.append({ 'tier': self.sum(i, 1), 'symbol': self.safe_symbol(marketId, market), 'currency': market['quote'], 'minNotional': minNotional, 'maxNotional': 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: dict, currency: Currency = None) -> TransferEntry: # # 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: dict = { '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: str, 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 ` """ return self.transfer(code, amount, 'future', 'spot', params) def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry: """ https://docs.kraken.com/api/docs/futures-api/trading/transfer https://docs.kraken.com/api/docs/futures-api/trading/sub-account-transfer 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 ` """ self.load_markets() currency = self.currency(code) if fromAccount == 'spot': raise BadRequest(self.id + ' transfer does not yet support transfers from spot') request: dict = { 'amount': amount, } response = None if toAccount == 'spot': if self.parse_account(fromAccount) != 'cash': raise BadRequest(self.id + ' transfer cannot transfer from ' + fromAccount + ' to ' + toAccount) request['currency'] = currency['id'] response = self.privatePostWithdrawal(self.extend(request, params)) else: request['fromAccount'] = self.parse_account(fromAccount) request['toAccount'] = self.parse_account(toAccount) request['unit'] = currency['id'] response = self.privatePostTransfer(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 set_leverage(self, leverage: int, symbol: Str = None, params={}): """ set the level of leverage for a market https://docs.kraken.com/api/docs/futures-api/trading/set-leverage-setting :param float leverage: the rate of leverage :param str symbol: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: response from the exchange """ if symbol is None: raise ArgumentsRequired(self.id + ' setLeverage() requires a symbol argument') self.load_markets() request: dict = { 'maxLeverage': leverage, 'symbol': self.market_id(symbol).upper(), } # # {result: "success", serverTime: "2023-08-01T09:40:32.345Z"} # return self.privatePutLeveragepreferences(self.extend(request, params)) def fetch_leverages(self, symbols: Strings = None, params={}) -> Leverages: """ fetch the set leverage for all contract and margin markets https://docs.kraken.com/api/docs/futures-api/trading/get-leverage-setting :param str[] [symbols]: a list of unified market symbols :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a list of `leverage structures ` """ self.load_markets() response = self.privateGetLeveragepreferences(params) # # { # "result": "success", # "serverTime": "2024-03-06T02:35:46.336Z", # "leveragePreferences": [ # { # "symbol": "PF_ETHUSD", # "maxLeverage": 30.00 # }, # ] # } # leveragePreferences = self.safe_list(response, 'leveragePreferences', []) return self.parse_leverages(leveragePreferences, symbols, 'symbol') def fetch_leverage(self, symbol: str, params={}) -> Leverage: """ fetch the set leverage for a market https://docs.kraken.com/api/docs/futures-api/trading/get-leverage-setting :param str symbol: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `leverage structure ` """ if symbol is None: raise ArgumentsRequired(self.id + ' fetchLeverage() requires a symbol argument') self.load_markets() market = self.market(symbol) request: dict = { 'symbol': self.market_id(symbol).upper(), } response = self.privateGetLeveragepreferences(self.extend(request, params)) # # { # "result": "success", # "serverTime": "2023-08-01T09:54:08.900Z", # "leveragePreferences": [{symbol: "PF_LTCUSD", maxLeverage: "5.00"}] # } # leveragePreferences = self.safe_list(response, 'leveragePreferences', []) data = self.safe_dict(leveragePreferences, 0, {}) return self.parse_leverage(data, market) def parse_leverage(self, leverage: dict, market: Market = None) -> Leverage: marketId = self.safe_string(leverage, 'symbol') leverageValue = self.safe_integer(leverage, 'maxLeverage') return { 'info': leverage, 'symbol': self.safe_symbol(marketId, market), 'marginMode': None, 'longLeverage': leverageValue, 'shortLeverage': leverageValue, } def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody): if response is None: return None if code == 429: raise DDoSProtection(self.id + ' ' + body) errors = self.safe_value(response, 'errors') firstError = self.safe_value(errors, 0) firtErrorMessage = self.safe_string(firstError, 'message') message = self.safe_string(response, 'error', firtErrorMessage) if message is None: return None 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 path == 'batchorder': postData = 'json=' + self.json(params) body = postData elif params: postData = self.urlencode(params) query += '?' + postData url = self.urls['api'][api] + query if api == 'private' or access == 'private': self.check_required_credentials() auth = postData + '/api/' if api != 'private': auth += api + '/' auth += 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/x-www-form-urlencoded', 'Accept': 'application/json', 'APIKey': self.apiKey, 'Authent': signature, } return {'url': url, 'method': method, 'body': body, 'headers': headers}