-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBittrexAutoSell.py
executable file
·284 lines (240 loc) · 7.94 KB
/
BittrexAutoSell.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
277
278
279
280
281
282
283
284
#!/usr/bin/env python3
import sys
import os.path
import json
import hashlib
import hmac
import base64
import requests as r
import arrow
if not os.path.exists("config.json"):
sys.exit(
"""
No config file found!
Please ensure \"config.json\" exists and is readable!
"""
)
# End if
with open("config.json", "r") as infile:
config = json.load(infile)
try:
assert config.get("APIToken","") != ""
assert config.get("FinalCoin","") != ""
except:
sys.exit(
"""
The \"config.json\" file exists, but is missing data.
Please verify this file contains a value for \"APIToken\" and \"FinalCoin\"
"""
)
# End try/except
# End with
def getMarkets():
"""
Make an unatuhenticated call to the Bittrex API to get a list
of all supported markets
"""
res = r.get("https://api.bittrex.com/api/v1.1/public/getmarkets")
res.raise_for_status()
return res.json()
# End def
def getTickerValues(Market):
"""
Make an unauthenticated call to get the current ticket values
for a specified market
"""
res = r.get("https://api.bittrex.com/api/v1.1/public/getticker?market=%s" % Market)
res.raise_for_status()
return res.json()
# End def
def filterMarkets(walletCoins, markets):
"""
Filter the list of all markets to only contain markets relevant to the
coins we hold balances in
"""
coinsWithBalances = []
monitoredMarkets = []
for wallet in walletCoins:
if wallet["Balance"] > 0.0:
coinsWithBalances.append(wallet["Currency"])
# End if
# End for
for market in markets:
if market["MarketCurrency"] in coinsWithBalances:
monitoredMarkets.append(market)
# End if
# End for
return monitoredMarkets
# End def
def getBalances(APIToken, IgnoredCoins=[]):
"""
Hit the Bittrex API to get a list of coin wallets with balances.
Filter any ignored coins from that list
Iterate over each remaining coin (if any) and process sell orders
for those coins
Example API return data:
{
"success": true,
"message": "''",
"result": [
{
"Currency": "DOGE",
"Balance": 4.21549076,
"Available": 4.21549076,
"Pending": 0,
"CryptoAddress": "DLxcEt3AatMyr2NTatzjsfHNoB9NT62HiF",
"Requested": false,
"Uuid": null
}
]
}
This function will return data as a list of dicts:
[
{
"Currency": "DOGE",
"Balance": 4.21549076,
"Available": 4.21549076,
"Pending": 0,
"CryptoAddress": "DLxcEt3AatMyr2NTatzjsfHNoB9NT62HiF",
"Requested": false,
"Uuid": null
}
]
"""
res = r.get("https://api.bittrex.com/api/v1.1/account/getbalances?apikey=%s" % APIToken)
res.raise_for_status
output = res.json()["result"]
balances = []
for crypto in output:
if IgnoredCoins and crypto["Currency"] not in IgnoredCoins:
balances.append(output[crypto])
# End if
# End for
return balances
# End def
def sellCoin(APIToken, APISecret, SourceCoin, DestCoin, Markets):
"""
APIToken: String
APISecret: String
SourceCoin: coin object
DestCoin: String
Markets: List of market objects
"""
def _sell(API_Token, APISecret, Market, Balance):
"""
This will post a "MARKET" order, which should be immediately filled at whatever market rate is.
"""
data = {
"marketSymbol": Market["MarketName"],
"direction": "SELL",
"type": "MARKET",
"quantity": Balance,
"timeInForce": "FILL_OR_KILL",
}
ts = arrow.now().timestamp * 1000 # Transforms ts from seconds into milliseconds
uri = "https://api.bittrex.com/v3/orders"
contentHash, signature = generateAuth(API_Token, APISecret, ts, str(data), uri, "POST")
headers = {
"Api-Key": API_Token,
"Api-Timestamp": ts;
"Api-Content-Hash": contentHash;
"Api-Signature": signature
}
r = requests.post(uri, data=data, headers=headers)
r.raise_for_status()
res = r.json()
return res["id"]
# End def
def _checkOrder(API_Token, APISecret, UUID):
"""
Returns true/false based on whether an order needs to be retried
"""
ts = arrow.now().timestamp * 1000 # Transforms ts from seconds into milliseconds
uri = "https://api.bittrex.com/v3/orders/%s" % UUID
contentHash, signature = generateAuth(API_Token, APISecret, ts, "", uri, "GET")
headers = {
"Api-Key": API_Token,
"Api-Timestamp": ts;
"Api-Content-Hash": contentHash;
"Api-Signature": signature
}
r = requests.get(uri, headers=headers)
r.raise_for_status()
res = r.json()
if res["quantity"] != res['fillQuantity']:
# Order didn't completely fill; need to signal that the rest should be sold somehow
#TODO
return True
elif res['fillQuantity'] == 0.0:
# Order was not filled at all; needs to be reattempted immediately
return False
else:
# Order filled completely
#TODO include timestamp and market in this logging message
print("Order filled successfully!")
return True
# End if/else block
# End def
sellMarkets = []
# First, check to see if direct market exists, for example "USDT-GRIN"
for market in markets:
if market["MarketName"] == "%s-%s" % (DestCoin, SourceCoin):
sellMarkets.append(market)
# End if
# End for
# If direct market doesn's exist, then we need to sell SourceCoin for BTC first and then sell BTC for DestCoin
if not sellMarkets:
for market in markets:
if market["MarketName"] == "%s-%s" % ("BTC", SourceCoin):
sellMarkets.append(market)
# End if
# End for
for market in markets:
if market["MarketName"] == "%s-%s" % ("BTC", DestCoin):
sellMarkets.append(market)
# End if
# End for
# End if
for market in sellMarkets:
completed = False
while not completed:
uuid = _sell(APIToken, APISecret, market, SourceCoin["Available"])
completed = _checkOrder(APIToken, APISecret, uuid)
# End while
# End for
# End def
def generateAuth(APIToken, APISecret, Timestamp, ContentToHash, URI, HTTPMethod, Subaccount_ID=""):
"""
To test hashing functions in JS:
var CryptoJS = require("crypto-js");
var contentHash = CryptoJS.SHA512("{'abc': 123}").toString(CryptoJS.enc.Hex);
contentHash;
"""
assert type(ContentToHash) == type("") # Our content should be a string
hashedContent = hashlib.sha512(ContentToHash.encode('utf-8')).hexdigest()
# Signature requires:
# Timestamp + URI + Method + hashedContent + Subaccount_ID
msg = """%s%s%s%s%s""" % (Timestamp, URI, HTTPMethod, hashedContent, Subaccount_ID)
digest = hmac.new(APISecret.encode(), msg=msg.encode(), digestmod=hashlib.sha512).hexdigest()
return (hashedContent, digest)
# End def
def main():
# Load config data
APIToken = config["APIToken"]
APISecret = config["APISecret"]
FinalCoin = config["FinalCoin"]
IgnoredCoins = config.get("IgnoredCoins",[])
# Get list of available markets
markets = getmarkets()
# Get list of coins to be sold
coinsToSell = getBalances(APIToken, IgnoredCoins)
# Filter list of all available markets to only contain relevant markets
relevantMarkets = filterMarkets(coinsToSell, markets)
# Sell any coins that need to be sold
for coin in coinsToSell:
sellCoin(APIToken, APISecret, coin, FinalCoin, relevantMarkets)
# End for
# End def
if __name__ == "__main__" :
main()
# End if