-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgemini.py
276 lines (227 loc) · 8.88 KB
/
gemini.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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
"""Python module for the Gemini API.
https://docs.gemini.com
@author Maneet Khaira
@email [email protected]
"""
import requests
import time
import base64
import hashlib
import json
import hmac
class GeminiSession:
"""Defines a Gemini API session.
A session uses one Gemini API key. An account can have multiple sessions.
"""
def __init__(self, api_key, api_secret, sandbox=False):
self.api_key = api_key
self.api_secret = api_secret
self.sandbox = sandbox
if sandbox is False:
self.api_url = 'https://api.gemini.com/v1/'
else:
self.api_url = 'https://api.sandbox.gemini.com/v1/'
def get_symbols(self):
"""Returns all available symbols for trading."""
try:
return requests.get(self.api_url + 'symbols').json()
except requests.exceptions.RequestException as e:
raise e
def get_ticker(self, symbol):
"""Returns recent trading activity for a symbol"""
try:
return requests.get(self.api_url + 'pubticker/' + symbol).json()
except requests.exceptions.RequestException as e:
raise e
def get_current_order_book(self, symbol, limit_bids=None, limit_asks=None):
"""Get the current order book
Args:
symbol (str): Symbol such as btcusd.
limit_bids (int): Optional. Max number of bids (offers to buy) to
return. Default is 50.
limit_asks (int): Optional. Max number of asks (offers to sell) to
return. Default is 50.
Returns:
A JSON object with two arrays, one for bids and one for asks.
"""
limits = {}
if limit_bids is not None:
limits["limit_bids"] = limit_bids
if limit_asks is not None:
limits["limit_asks"] = limit_asks
try:
return requests.get(self.api_url + symbol, params=limits).json()
except requests.exceptions.RequestException as e:
raise e
def new_order(self, symbol, amount, price, side, client_order_id=None,
order_execution=None):
"""Place a new order
Args:
symbol (str): Symbol such as btcusd.
amount (str): Decimal amount of BTC to purchase. Note that this
should be a string.
price (str): Decimal amount of USD to spend per BTC. Note that this
should be a string.
side (str): Must be "buy" or "sell"
order_execution (str): Optional. Order execution option.
"maker-or-cancel" and "immediate-or-cancel" are the currently
supported options.
Returns:
A JSON object with information about the order.
"""
fields = {
'request': '/v1/order/new',
'nonce': self._nonce(),
# Request-specific items
'symbol': symbol, # Any symbol from the /symbols API
'amount': amount, # Once again, a quoted number
'price': price,
'side': side, # Must be "buy" or "sell"
# The order type; only "exchange limit" supported
'type': 'exchange limit'
}
if client_order_id is not None:
fields['client_order_id'] = client_order_id
if order_execution is not None:
fields['order_execution'] = [order_execution]
try:
return requests.post(self.api_url + 'order/new',
headers=self._create_payload(fields)).json()
except requests.exceptions.RequestException as e:
raise e
def cancel_order(self, order_id):
"""Cancels an existing order with the given order_id"""
fields = {
'request': '/v1/order/cancel',
'nonce': self._nonce(),
'order_id': order_id
}
try:
return requests.post(self.api_url + 'order/cancel',
headers=self._create_payload(fields)).json()
except requests.exceptions.RequestException as e:
raise e
def cancel_all_session_orders(self):
"""Cancels all orders for the current session"""
fields = {
'request': '/v1/order/cancel/session',
'nonce': self._nonce(),
}
try:
return requests.post(self.api_url + 'order/cancel/session',
headers=self._create_payload(fields)).json()
except requests.exceptions.RequestException as e:
raise e
def cancel_all_active_orders(self):
"""Cancels all orders across all sessions"""
fields = {
'request': '/v1/order/cancel/all',
'nonce': self._nonce(),
}
try:
return requests.post(self.api_url + 'order/cancel/all',
headers=self._create_payload(fields)).json()
except requests.exceptions.RequestException as e:
raise e
def get_order_status(self, order_id):
"""Get the status of an order with given order_id"""
fields = {
'request': '/v1/order/status',
'nonce': self._nonce(),
'order_id': order_id
}
try:
return requests.post(self.api_url + 'order/status',
headers=self._create_payload(fields)).json()
except requests.exceptions.RequestException as e:
raise e
def get_all_order_status(self):
"""Get the status of all active orders"""
fields = {
'request': '/v1/order',
'nonce': self._nonce(),
}
try:
return requests.post(self.api_url + 'order',
headers=self._create_payload(fields)).json()
except requests.exceptions.RequestException as e:
raise e
def get_past_trades(self, symbol, limit_trades=None, timestamp=None):
"""Returns information about past trades.
Args:
limit_trades (int): Optional. The max number of trades to return.
Default is 50, max is 100.
timestamp (int): Optional. Can be provided to only return trades
after timestamp. Can be in milliseconds or seconds.
Returns:
Array of trade information items.
"""
fields = {
'request': '/v1/mytrades',
'nonce': self._nonce(),
'symbol': symbol,
}
if limit_trades is not None:
fields["limit_trades"] = limit_trades
if timestamp is not None:
fields["timestamp"] = timestamp
try:
return requests.post(self.api_url + 'mytrades',
headers=self._create_payload(fields)).json()
except requests.exceptions.RequestException as e:
raise e
def get_trade_volume(self):
"""Get trade volume information for the account
Returns:
Array where each element contains information about one day of
trading activity.
"""
fields = {
'request': '/v1/tradevolume',
'nonce': self._nonce(),
}
try:
return requests.post(self.api_url + 'tradevolume',
headers=self._create_payload(fields)).json()
except requests.exceptions.RequestException as e:
raise e
def get_balances(self):
"""Get available balances in the supported currencies
Returns:
Array where each element is for a different currency.
"""
fields = {
'request': '/v1/balances',
'nonce': self._nonce(),
}
try:
return requests.post(self.api_url + 'balances',
headers=self._create_payload(fields)).json()
except requests.exceptions.RequestException as e:
raise e
def heartbeat(self):
"""Prevents a session from timing out if the require heartbeat flag is
set when the API key was provisioned.
"""
fields = {
'request': '/v1/heartbeat',
'nonce': self._nonce(),
}
try:
return requests.post(self.api_url + 'heartbeat',
headers=self._create_payload(fields)).json()
except requests.exceptions.RequestException as e:
raise e
def _nonce(self):
# Creates a 'nonce' by getting system time
return int(round(time.time() * 1000))
def _create_payload(self, fields):
# Formats the headers as specified by the Gemini API
encodedFields = base64.b64encode(json.dumps(fields).encode())
headers = {
'X-GEMINI-APIKEY': self.api_key,
'X-GEMINI-PAYLOAD': encodedFields,
'X-GEMINI-SIGNATURE': hmac.new(self.api_secret.encode(),
encodedFields, digestmod=hashlib.sha384).hexdigest()
}
return headers