292 lines
11 KiB
Python
292 lines
11 KiB
Python
import time
|
|
import base64
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
import urllib.request as urllib2
|
|
import urllib.parse as urllib
|
|
import ssl
|
|
|
|
|
|
class KrakenFutureClient(object):
|
|
def __init__(self, api_key="", api_secret="", timeout=10, checkCertificate=True, useNonce=False):
|
|
self.apiPath = 'https://futures.kraken.com'
|
|
self._api_key = api_key
|
|
self._api_secret = api_secret
|
|
self.timeout = timeout
|
|
self.nonce = 0
|
|
self.checkCertificate = checkCertificate
|
|
self.useNonce = useNonce
|
|
|
|
''' public endpoints '''
|
|
|
|
# returns all instruments with specifications
|
|
def get_instruments(self):
|
|
endpoint = "/derivatives/api/v3/instruments"
|
|
return self.make_request("GET", endpoint)['instruments']
|
|
|
|
# returns market data for all instruments
|
|
def get_tickers(self):
|
|
endpoint = "/derivatives/api/v3/tickers"
|
|
return self.make_request("GET", endpoint)
|
|
|
|
# returns the entire order book of a futures
|
|
def get_orderbook(self, symbol):
|
|
endpoint = "/derivatives/api/v3/orderbook"
|
|
postUrl = "symbol=%s" % symbol
|
|
return self.make_request("GET", endpoint, postUrl=postUrl)
|
|
|
|
# returns historical data for futures and indices
|
|
def get_history(self, symbol, lastTime=""):
|
|
endpoint = "/derivatives/api/v3/history"
|
|
if lastTime != "":
|
|
postUrl = "symbol=%s&lastTime=%s" % (symbol, lastTime)
|
|
else:
|
|
postUrl = "symbol=%s" % symbol
|
|
return self.make_request("GET", endpoint, postUrl=postUrl)
|
|
|
|
''' private endpoints '''
|
|
|
|
# returns key account information
|
|
# Deprecated because it returns info about the Futures margin account
|
|
# Use get_accounts instead
|
|
def get_account(self):
|
|
endpoint = "/derivatives/api/v3/account"
|
|
return self.make_request("GET", endpoint)
|
|
|
|
# returns key account information
|
|
def get_accounts(self):
|
|
endpoint = "/derivatives/api/v3/accounts"
|
|
return self.make_request("GET", endpoint)['accounts']
|
|
|
|
# places an order
|
|
def send_order(self, orderType, symbol, side, size, limitPrice, stopPrice=None, clientOrderId=None):
|
|
endpoint = "/derivatives/api/v3/sendorder"
|
|
postBody = "orderType=%s&symbol=%s&side=%s&size=%s&limitPrice=%s" % (
|
|
orderType, symbol, side, size, limitPrice)
|
|
|
|
if orderType == "stp" and stopPrice is not None:
|
|
postBody += "&stopPrice=%s" % stopPrice
|
|
|
|
if clientOrderId is not None:
|
|
postBody += "&cliOrdId=%s" % clientOrderId
|
|
|
|
return self.make_request("POST", endpoint, postBody=postBody)
|
|
|
|
# places an order
|
|
def send_order_1(self, order):
|
|
endpoint = "/derivatives/api/v3/sendorder"
|
|
postBody = urllib.urlencode(order)
|
|
return self.make_request("POST", endpoint, postBody=postBody)
|
|
|
|
# edit an order
|
|
def edit_order(self, edit):
|
|
endpoint = "/derivatives/api/v3/editorder"
|
|
postBody = urllib.urlencode(edit)
|
|
return self.make_request("POST", endpoint, postBody=postBody)
|
|
|
|
# cancels an order
|
|
def cancel_order(self, order_id=None, cli_ord_id=None):
|
|
endpoint = "/derivatives/api/v3/cancelorder"
|
|
|
|
if order_id is None:
|
|
postBody = "cliOrdId=%s" % cli_ord_id
|
|
else:
|
|
postBody = "order_id=%s" % order_id
|
|
|
|
return self.make_request("POST", endpoint, postBody=postBody)
|
|
|
|
# cancel all orders
|
|
def cancel_all_orders(self, symbol=None):
|
|
endpoint = "/derivatives/api/v3/cancelallorders"
|
|
if symbol is not None:
|
|
postbody = "symbol=%s" % symbol
|
|
else:
|
|
postbody = ""
|
|
|
|
return self.make_request("POST", endpoint, postBody=postbody)
|
|
|
|
# cancel all orders after
|
|
def cancel_all_orders_after(self, timeoutInSeconds=60):
|
|
endpoint = "/derivatives/api/v3/cancelallordersafter"
|
|
postbody = "timeout=%s" % timeoutInSeconds
|
|
|
|
return self.make_request("POST", endpoint, postBody=postbody)
|
|
|
|
# places or cancels orders in batch
|
|
def send_batchorder(self, jsonElement):
|
|
endpoint = "/derivatives/api/v3/batchorder"
|
|
postBody = "json=%s" % jsonElement
|
|
return self.make_request("POST", endpoint, postBody=postBody)
|
|
|
|
# returns all open orders
|
|
def get_openorders(self):
|
|
endpoint = "/derivatives/api/v3/openorders"
|
|
return self.make_request("GET", endpoint)
|
|
|
|
# returns filled orders
|
|
def get_fills(self, lastFillTime=""):
|
|
endpoint = "/derivatives/api/v3/fills"
|
|
if lastFillTime != "":
|
|
postUrl = "lastFillTime=%s" % lastFillTime
|
|
else:
|
|
postUrl = ""
|
|
return self.make_request("GET", endpoint, postUrl=postUrl)
|
|
|
|
# returns all open positions
|
|
def get_openpositions(self):
|
|
endpoint = "/derivatives/api/v3/openpositions"
|
|
return self.make_request("GET", endpoint)
|
|
|
|
# sends an xbt withdrawal request
|
|
def send_withdrawal(self, targetAddress, currency, amount):
|
|
endpoint = "/derivatives/api/v3/withdrawal"
|
|
postBody = "targetAddress=%s¤cy=%s&amount=%s" % (
|
|
targetAddress, currency, amount)
|
|
return self.make_request("POST", endpoint, postBody=postBody)
|
|
|
|
# returns xbt transfers
|
|
def get_transfers(self, lastTransferTime=""):
|
|
endpoint = "/derivatives/api/v3/transfers"
|
|
if lastTransferTime != "":
|
|
postUrl = "lastTransferTime=%s" % lastTransferTime
|
|
else:
|
|
postUrl = ""
|
|
return self.make_request("GET", endpoint, postUrl=postUrl)
|
|
|
|
# returns all notifications
|
|
def get_notifications(self):
|
|
endpoint = "/derivatives/api/v3/notifications"
|
|
return self.make_request("GET", endpoint)
|
|
|
|
# makes an internal transfer
|
|
def transfer(self, fromAccount, toAccount, unit, amount):
|
|
endpoint = "/derivatives/api/v3/transfer"
|
|
postBody = "fromAccount=%s&toAccount=%s&unit=%s&amount=%s" % (
|
|
fromAccount, toAccount, unit, amount)
|
|
return self.make_request("POST", endpoint, postBody=postBody)
|
|
|
|
# accountlog csv
|
|
def get_accountlog(self):
|
|
endpoint = "/api/history/v2/accountlogcsv"
|
|
return self.make_request("GET", endpoint)
|
|
|
|
def _get_partial_historical_elements(self, elementType, since=None, contToken=None):
|
|
endpoint = "/api/history/v2/%s" % elementType
|
|
|
|
if contToken is not None:
|
|
return self.make_request_raw("GET", endpoint, postUrl="continuationToken=%s" % contToken)
|
|
else:
|
|
if since is not None:
|
|
return self.make_request_raw("GET", endpoint, postUrl="since=%s" % since)
|
|
|
|
return self.make_request_raw("GET", endpoint)
|
|
|
|
def _get_historical_elements(self, elementType, since=None):
|
|
elements = []
|
|
|
|
more = True
|
|
contToken = None
|
|
|
|
while more:
|
|
res = self._get_partial_historical_elements(elementType, since, contToken)
|
|
body = json.loads(res.read().decode('utf-8'))
|
|
elements = elements + body['elements']
|
|
|
|
if res.headers['is-truncated'] is None or res.headers['is-truncated'] == "false":
|
|
more = False
|
|
contToken = None
|
|
else:
|
|
contToken = res.headers['next-continuation-token']
|
|
|
|
elements.sort(key=lambda el: el['timestamp'], reverse=True)
|
|
return elements
|
|
|
|
# historical orders after a certain point in reverse chronological order
|
|
def get_historical_orders(self, since=None):
|
|
return self._get_historical_elements('orders', since)
|
|
|
|
# recent orders in reverse chronological order
|
|
def get_recent_orders(self):
|
|
return self.get_historical_orders(None)
|
|
|
|
# historical executions after a certain point in reverse chronological order
|
|
def get_historical_executions(self, since=None):
|
|
return self._get_historical_elements('executions', since)
|
|
|
|
# recent executions in reverse chronological order
|
|
def get_recent_executions(self):
|
|
return self.get_historical_executions(None)
|
|
|
|
# signs a message
|
|
def sign_message(self, endpoint, postData, nonce=""):
|
|
if endpoint.startswith('/derivatives'):
|
|
endpoint = endpoint[len('/derivatives'):]
|
|
|
|
# step 1: concatenate postData, nonce + endpoint
|
|
message = postData + nonce + endpoint
|
|
|
|
# step 2: hash the result of step 1 with SHA256
|
|
sha256_hash = hashlib.sha256()
|
|
sha256_hash.update(message.encode('utf8'))
|
|
hash_digest = sha256_hash.digest()
|
|
|
|
# step 3: base64 decode apiPrivateKey
|
|
secretDecoded = base64.b64decode(self._api_secret)
|
|
|
|
# step 4: use result of step 3 to has the result of step 2 with HMAC-SHA512
|
|
hmac_digest = hmac.new(secretDecoded, hash_digest,
|
|
hashlib.sha512).digest()
|
|
|
|
# step 5: base64 encode the result of step 4 and return
|
|
return base64.b64encode(hmac_digest)
|
|
|
|
# creates a unique nonce
|
|
def get_nonce(self):
|
|
# https://en.wikipedia.org/wiki/Modulo_operation
|
|
self.nonce = (self.nonce + 1) & 8191
|
|
return str(int(time.time() * 1000)) + str(self.nonce).zfill(4)
|
|
|
|
# sends an HTTP request
|
|
def make_request_raw(self, requestType, endpoint, postUrl="", postBody=""):
|
|
# create authentication headers
|
|
postData = postUrl + postBody
|
|
|
|
if self.useNonce:
|
|
nonce = self.get_nonce()
|
|
signature = self.sign_message(endpoint, postData, nonce=nonce)
|
|
authentHeaders = {"APIKey": self._api_key,
|
|
"Nonce": nonce, "Authent": signature}
|
|
else:
|
|
signature = self.sign_message(endpoint, postData)
|
|
authentHeaders = {
|
|
"APIKey": self._api_key, "Authent": signature}
|
|
|
|
authentHeaders["User-Agent"] = "cf-api-python/1.0"
|
|
|
|
# create request
|
|
if postUrl != "":
|
|
url = self.apiPath + endpoint + "?" + postUrl
|
|
else:
|
|
url = self.apiPath + endpoint
|
|
|
|
request = urllib2.Request(url, str.encode(postBody), authentHeaders)
|
|
request.get_method = lambda: requestType
|
|
|
|
# read response
|
|
if self.checkCertificate:
|
|
response = urllib2.urlopen(request, timeout=self.timeout)
|
|
else:
|
|
ctx = ssl.create_default_context()
|
|
ctx.check_hostname = False
|
|
ctx.verify_mode = ssl.CERT_NONE
|
|
response = urllib2.urlopen(
|
|
request, context=ctx, timeout=self.timeout)
|
|
|
|
# return
|
|
return response
|
|
|
|
# sends an HTTP request and read response body
|
|
def make_request(self, requestType, endpoint, postUrl="", postBody=""):
|
|
output = self.make_request_raw(requestType, endpoint, postUrl, postBody)
|
|
return json.loads(output.read().decode('utf-8')) |