-
Notifications
You must be signed in to change notification settings - Fork 5
/
bitfinex.py
134 lines (114 loc) · 5.56 KB
/
bitfinex.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
from .base import BaseExchangeApi, ExchangeApiException
import base64
import re
import json
import ujson
class BitfinexApi(BaseExchangeApi):
def get_symbol(self, stake_currency, trade_currency):
return self.make_bitfinex_symbol(trade_currency + "/" + stake_currency)
# return "t{}{}".format(trade_currency, stake_currency)
def unmake_bitfinex_symbol(self, bitfinex_symbol):
assert re.match("t[A-Z]{3,}[:]?[A-Z]{3,}", bitfinex_symbol), (
"Format of bitfinex_symbol should be t$trade_currency$stake_currency or t$trade_currency:$stake_currency"
" (for TESTBTC, etc papertrading symbols): {}".format(bitfinex_symbol)
)
if ":TEST" in bitfinex_symbol:
# tTESTBTC:TESTUSDT -> TESTBTC/TESTUSDT
pieces = bitfinex_symbol.lstrip("t").split(":")
return "{}/{}".format(pieces[0], pieces[1])
else:
# tETHUSD -> ETH/USD
return "{}/{}".format(bitfinex_symbol[1:-3], bitfinex_symbol[-3:])
def make_bitfinex_symbol(self, symbol):
assert re.match(
"[A-Z]{3,}/[A-Z]{3,}", symbol
), "Format of symbol should be $trade_currency/$stake_currency: {}".format(symbol)
pieces = symbol.split("/")
if "TEST" in pieces[0]: # we're in bitfinex papertrading land, put a : between symbols
# TESTBTC/TESTUSDT -> tTESTBTC:TESTUSDT
return "t{}:{}".format(pieces[0], pieces[1])
else:
# ETH/USD -> tETHUSD
return "t{}{}".format(pieces[0], pieces[1])
def brequest(
self, api_version, endpoint=None, authenticate=False, method="GET", params=None, data=None,
):
# Inspired by https://raw.githubusercontent.com/faberquisque/pyfinex/master/pyfinex/api.py
# Handle requests for both v1 and v2 versions of the API with one wrapper
# Why both, you ask? v2 has better data, but does not support write requests (only in v2 websockets API)
# So we have to use v1 for anything that writes
assert api_version in [1, 2]
assert not endpoint.startswith("/v"), "endpoint should not be a full path, but the url after v1/v2"
base_url = "https://api.bitfinex.com"
if api_version == 2 and authenticate is False:
base_url = "https://api-pub.bitfinex.com"
api_path = "/v%s/%s" % (api_version, endpoint)
headers = self.DEFAULT_HEADERS
# Required because data for the signature must match the data that is passed in the body as json, even if empty
data = data or {}
if authenticate:
nonce = self.nonce()
payload = self.generate_payload(api_version, api_path, nonce, data)
headers.update(self.auth_headers(self.key, self.secret, api_version, nonce, payload))
url = base_url + api_path
try:
return self.request(url, method, params, data, headers)
except ExchangeApiException as exc:
if exc.message in ["nonce: small", "Nonce is too small."]:
raise BitfinexNonceException(method, url, exc.status_code, exc.message)
else:
raise
def generate_payload(self, api_version, api_path, nonce, data):
""" Return the header's payload based on version """
assert api_version in [1, 2]
if api_version == 1:
payload_object = {}
payload_object["request"] = api_path
payload_object["nonce"] = nonce
payload_object.update(data)
# Important to use json here, the format has to match what bitfinex expects
# (ujson strips extra space, which causes invalid api key)
payload = json.dumps(payload_object)
elif api_version == 2:
# same note here about json
payload = "/api" + api_path + nonce + json.dumps(data)
return payload
def auth_headers(self, key, secret, api_version, nonce, payload):
""" Return the request headers based on version """
assert api_version in [1, 2]
headers = {}
if api_version == 1:
message = base64.b64encode(payload.encode("utf8"))
headers["X-BFX-APIKEY"] = key
headers["X-BFX-PAYLOAD"] = message
headers["X-BFX-SIGNATURE"] = self.sign(secret, message)
elif api_version == 2:
message = payload.encode("utf8")
headers["bfx-nonce"] = nonce
headers["bfx-apikey"] = key
headers["bfx-signature"] = self.sign(secret, message)
return headers
def parse_error_text(self, response):
# Exchange-specific error handling
try:
error_json = ujson.loads(response.content)
# V1
# {"message":"Nonce is too small."}
if "message" in error_json:
return error_json["message"]
# V2
# ["error", 20060, "maintenance"]
if type(error_json) == list and error_json[0] == "error":
return error_json[2]
# Valid json, but unrecognized format, just return it
return response.text
except ValueError:
# HTML? Probably an error page from cloudflare. Grab the title if we can
match = re.search("<title.*?>([^<]+)</title>", response.text)
if match:
return "HTML: %s" % match.groups()[0]
else:
# No title. We need to truncate in case it's an entire html page
return (response.text[:50] + "...") if len(response.text) > 50 else response.text
class BitfinexNonceException(ExchangeApiException):
pass