Files
ccxt_with_mt5/ccxt/krakenfutures.py
lz_db 0fab423a18 add
2025-11-16 12:31:03 +08:00

2783 lines
121 KiB
Python

# -*- 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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=ticker-structure>`
"""
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 <https://docs.ccxt.com/#/?id=trade-structure>`
"""
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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
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 <https://docs.ccxt.com/#/?id=trade-structure>`
"""
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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=funding-rate-structure>`
"""
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 <https://docs.ccxt.com/#/?id=funding-rate-history-structure>` to fetch
:param dict [params]: extra parameters specific to the api endpoint
:returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>`
"""
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 <https://docs.ccxt.com/#/?id=leverage-tiers-structure>`, 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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=leverage-structure>`
"""
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 <https://docs.ccxt.com/#/?id=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}