2783 lines
121 KiB
Python
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}
|