-
Notifications
You must be signed in to change notification settings - Fork 0
/
bit2c_api_caller.py
496 lines (429 loc) · 24 KB
/
bit2c_api_caller.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
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
#############################################################################
#
# Version 0.1.2 - Author: Asaf Ravid <asaf.rvd@gmail.com>
#
# Copyright (C) 2021 Asaf Ravid
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
#############################################################################
import requests
import time
import ccxt
import matplotlib.pyplot as plt
import re
PAIRS = ['BtcNis', 'EthNis', 'BchabcNis', 'LtcNis', 'EtcNis', 'BtgNis', 'BchsvNis', 'GrinNis']
PAIRS_FOR_TRADE = [ True, True, False, True, False, False, False, False ]
BALANCE_TARGET = [ 0.4, 0.4, 0, 0.05, 0, 0, 0, 0, ]
NAMES = ['BTC', 'ETH', 'BCHABC', 'LTC', 'ETC', 'BTG', 'BCH', 'GRIN' ]
NORMAL = 0
BUY_ONLY = 1
CANCEL_ALL = 2
BUY_SELL_PROFIT_WITH_PATIENCE = 3
# Normal Mode:
# EXISTING_INDICATOR_PERCENT = 3.0
# COMMISSION_PERCENT = 1.0
# COMMISSION_FACTOR = 10.0
# COMMISSION_PERCENT_THRESHOLD = round(float(COMMISSION_PERCENT*COMMISSION_FACTOR), 4)
# BUY_ABOVE_COMMISSION = round(float(1)+float(COMMISSION_PERCENT)/100, 4)
# SELL_BELOW_COMMISSION = round(float(1)-float(COMMISSION_PERCENT)/100, 4)
# ORDER_PERCENTAGE = 2.5 # percentage of balance to spend on recommended order
# MINIMUM_ORDER_NIS = 45.0
# NUM_ITERATIONS_AFTER_WHICH_TO_CHECK_SANITY = 5 # After 6 iterations, if there is no change in number of requried orders, then the quota of open orders was likely to have been reached
# SPREAD_ADVANCE_FACTOR = 1.0 # COMMISSION_FACTOR / (2*NUM_ITERATIONS_AFTER_WHICH_TO_CHECK_SANITY)
# RUN_ONCE = False
# CANCEL_ALL_ORDERS = False
EXISTING_INDICATOR_PERCENT = 3.0
COMMISSION_PERCENT = 1.0
COMMISSION_FACTOR = 10.0
COMMISSION_PERCENT_THRESHOLD = round(float(COMMISSION_PERCENT*COMMISSION_FACTOR), 4)
BUY_ABOVE_COMMISSION = round(float(1)+float(COMMISSION_PERCENT)/100, 4)
SELL_BELOW_COMMISSION = round(float(1)-float(COMMISSION_PERCENT)/100, 4)
ORDER_PERCENTAGE = 10.0 # percentage of balance to spend on recommended order
MINIMUM_ORDER_NIS = 250.0
NUM_ITERATIONS_AFTER_WHICH_TO_CHECK_SANITY = 6 # After 6 iterations, if there is no change in number of requried orders, then the quota of open orders was likely to have been reached
SPREAD_ADVANCE_FACTOR = 0.0 # COMMISSION_FACTOR / (2*NUM_ITERATIONS_AFTER_WHICH_TO_CHECK_SANITY)
RUN_ONCE = True
CANCEL_ALL_ORDERS = False
MODE = BUY_SELL_PROFIT_WITH_PATIENCE
GRAPH_ONLY = False
GRAPH_PERIOD_SEC = 10
INFORMATIVE_ONLY = False
DISCOUNT_BUY_PERCENTAGE = 20.0
# Routine Mode (Comment if required):
GRAPH_ONLY = True
INFORMATIVE_ONLY = True
RUN_ONCE = False
VISUAL_MODE = True
if GRAPH_ONLY is False:
GRAPH_PERIOD_SEC = 10
def bit2c_classic_margins(endless_mode):
headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/61.0.3163.79 '
'Safari/537.36'} # 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36'}
session = requests.Session()
session.headers.update(headers)
while True:
spread_orders = {}
print("\n\n[bit2c_classic_margins] Recommendations:\n=========================================")
for pair in PAIRS:
url = 'https://bit2c.co.il/Exchanges/{}/orderbook.json'.format(pair)
try:
r = session.get(url) # common.DEFAULT_HEADERS)
r.raise_for_status()
data = r.json()
except:
print("[bit2c_classic_margins] Exception: Failed to Connect to url {}".format(url))
return
if len(data["bids"]) == 0 or len(data["asks"]) == 0:
continue
if PAIRS_FOR_TRADE[PAIRS.index(pair)] is False:
continue
highest_bid = data["bids"][0][0]
lowest_ask = data["asks"][0][0]
spread = round(100*(float(lowest_ask)-float(highest_bid))/float(lowest_ask),4)
adapted_pair = pair.replace('Nis', '/Nis').upper().replace('BCHABC', 'BCH').replace('BCHSV', 'BSV')
if MODE == BUY_SELL_PROFIT_WITH_PATIENCE:
spread_orders[adapted_pair] = {
'buy' : { 'at': round(highest_bid*(1-DISCOUNT_BUY_PERCENTAGE/100), 2) },
'sell': { 'at': round(lowest_ask *(1+DISCOUNT_BUY_PERCENTAGE/100), 2) }
}
print('[bit2c_classic_margins] spread_orders[{}] = buy at {:8}, sell at {:8}. bid/ask = {:8} / {:8}. Spread = {}%'.format(adapted_pair, spread_orders[adapted_pair]['buy']['at'], spread_orders[adapted_pair]['sell']['at'], highest_bid, lowest_ask, DISCOUNT_BUY_PERCENTAGE))
else:
if spread > COMMISSION_PERCENT_THRESHOLD:
# print("{:10}: [bid, ask] = [{:10}, {:10}], spread[%]={:10}".format(pair, highest_bid, lowest_ask, spread))
buy_at = round(highest_bid*BUY_ABOVE_COMMISSION, 2)
sell_at = round(lowest_ask *SELL_BELOW_COMMISSION, 2)
print(" SELL {:10} at {:10}".format(pair.replace('Nis', '').upper(), sell_at))
print(" BUY {:10} at {:10}".format(pair.replace('Nis', '').upper(), buy_at))
print(" ===============================")
spread_orders[adapted_pair] = {
'buy' : { 'at': buy_at },
'sell': { 'at': sell_at }
}
if not endless_mode:
print("\n")
return spread_orders
time.sleep(1)
API_1 = {
'key': '<fill in>',
'secret': '<fill in>'
}
def connect_to_bit2c(api):
# from variable id
exchange_id = 'bit2c'
exchange_class = getattr(ccxt, exchange_id)
exchange = exchange_class({
'apiKey' : api["key"].upper(),
'secret' : api["secret"],
'timeout' : 30000,
'enableRateLimit': True,
})
return exchange
def get_balances(exchange, plot, balances):
print('[get_balances] Fetching Balances...\n=======================================')
for refresh_seconds_left in range(GRAPH_PERIOD_SEC, 1, -1):
time.sleep(1)
print('\rrefresh_seconds_left: ', refresh_seconds_left, end='')
balances['data'] = exchange.fetch_balance()
print('\n[get_balances] Balances: {}'.format(balances))
labels = []
sizes = []
explodes = []
colors = []
nis_values = []
names = []
indices = []
coin_values = []
total_nis = 0
color_index = 0
colors_list = ['brown', 'blue', 'lightcoral', 'lightskyblue', 'darkgreen', 'red', 'orange']
NUM_ELEMENTS_IN_COIN = 4 # AVAILABLE_<Coin>, <Coin>, LOCKED_<Coin>, ESTIMATED_BALANCE_<Coin>_IN_NIS
element_in_coin = 0
for item, value in balances['data']['info'].items():
if 'BCHABC' in item: continue
if 'BCHSV' in item: continue
if 'BTG' in item: continue
if 'ETC' in item: continue
if element_in_coin == 1:
total_coin = float(value)
if 'ESTIMATED_BALANCE_' in item:
# print('[get_balances] type(value) = {}, value = {}'.format(type(value), value))
value = float(value)
coin_name = item.replace('ESTIMATED_BALANCE_', '').replace('_IN_NIS', '').replace('ABC', '').replace('GRIN', 'GRN') + ' = {:7} NIS'.format(int(round(value,0)))
item_name = '{:7} '.format(round(total_coin,4)) + coin_name + ' (@ {:8} NIS)'.format(round(value/total_coin,4))
print(' {}'.format(item_name))
labels.append(re.sub(' +', ' ', item_name)) # Remove extra whitespaces
sizes.append(value)
colors.append(colors_list[color_index])
explodes.append(0)
total_nis += value
currency_name = coin_name.split("=",1)[0].replace(' ','')
if currency_name != 'NIS':
indices.append(NAMES.index(currency_name))
names.append(currency_name)
coin_values.append(round(value / total_coin, 4))
nis_values.append(value)
color_index += 1
element_in_coin += 1
if element_in_coin >= NUM_ELEMENTS_IN_COIN: element_in_coin = 0
ratios_in_total = [round(x / total_nis,3) for x in nis_values]
print( '\n[get_balances] names = ', end='')
for index,value in enumerate(names ): print('{:>10} | '.format(value), end='')
print( '\n----------------------------------------------------------------------------')
print( '\n[get_balances] coin_values = ', end='')
for index,value in enumerate(coin_values ): print('{:>10} | '.format(value), end='')
print( '\n----------------------------------------------------------------------------')
print( '\n[get_balances] ratios_in_total = ', end='')
for index,value in enumerate(ratios_in_total): print('{:>10} | '.format(value), end='')
print( '\n[get_balances] nis_values = ', end='')
for index,value in enumerate(nis_values ): print('{:>10} | '.format(int(round(value,0))), end='')
# print('\n[get_balances] indices = ', end='')
# for index,value in enumerate(indices ): print('{:>10} | '.format(value), end='')
nis_deltas = []
coin_deltas = []
for index, item in enumerate(ratios_in_total):
current_ratio = item
required_ratio = BALANCE_TARGET[indices[index]]
delta = (required_ratio-current_ratio)*total_nis
nis_deltas.append(round(delta,4))
coin_delta = delta/coin_values[index]
coin_deltas.append(round(coin_delta,5))
print( '\n----------------------------------------------------------------------------')
print( '\n[get_balances] nis_deltas = ', end='')
for index,value in enumerate(nis_deltas ): print('{:>10} | '.format(int(round(value,0))), end='')
print( '\n[get_balances] coin_deltas = ', end='')
for index,value in enumerate(coin_deltas ): print('{:>10} | '.format(value), end='')
print( '\n')
if plot:
plt.style.use('dark_background')
plt.clf()
plt.pie(sizes, labels=labels, colors=colors, explode=explodes, autopct='%1.1f%%', shadow=True, startangle=180)
plt.axis('equal')
plt.show(block=False)
for refresh_seconds_left in range(GRAPH_PERIOD_SEC, 1, -1):
plt.title('Total value: {} NIS (Refresh in {} sec)'.format(int(round(total_nis,0)), refresh_seconds_left))
plt.pause(1)
plt.close
print("\n")
def cancel_my_open_orders(exchange, markets):
my_open_orders = {}
for market in markets:
adapted_market = market.replace('BTC', 'Btc').replace('ETH', 'Eth').replace('BCH', 'Bchabc').replace('LTC', 'Ltc').replace('ETC', 'Etc').replace('BTG', 'Btg').replace('BSV', 'Bchsv').replace('GRIN', 'Grin').replace('/NIS', 'Nis')
if PAIRS_FOR_TRADE[PAIRS.index(adapted_market)] is False:
continue
print("[cancel_my_open_orders] Fetching {} Orders".format(market))
open_orders = exchange.fetch_open_orders(market)
if len(open_orders):
print('[cancel_my_open_orders] {} orders are {}'.format(market, open_orders))
for order in open_orders:
print(' Cancelling [{:4} {:6} {:9} at {:5}]'.format(order['side'], order['amount'], order['symbol'], order['price']))
exchange.cancel_order(order['id'])
print("\n")
def scan_my_open_orders(exchange, markets, return_only_edges=True):
my_open_orders = {}
for market in markets:
adapted_market = market.replace('BTC', 'Btc').replace('ETH', 'Eth').replace('BCH', 'Bchabc').replace('LTC', 'Ltc').replace('ETC', 'Etc').replace('BTG', 'Btg').replace('BSV', 'Bchsv').replace('GRIN', 'Grin').replace('/NIS', 'Nis')
if PAIRS_FOR_TRADE[PAIRS.index(adapted_market)] is False:
continue
open_orders = exchange.fetch_open_orders(market)
print('[scan_my_open_orders] Scanning {} open {} orders'.format(len(open_orders), market))
if len(open_orders):
my_lowest_sell = None
my_highest_buy = None
my_open_orders_for_this_market = []
for order in open_orders:
my_order = {
'side' : order['side'],
'amount': order['amount'],
'price' : order['price']
}
if return_only_edges:
if order['side'] == 'sell':
if my_lowest_sell is None:
my_lowest_sell = my_order
elif my_order['price'] < my_lowest_sell['price']:
my_lowest_sell = my_order
elif order['side'] == 'buy':
if my_highest_buy is None:
my_highest_buy = my_order
elif my_order['price'] > my_highest_buy['price']:
my_highest_buy = my_order
else:
my_open_orders_for_this_market.append(my_order)
if return_only_edges:
if my_lowest_sell is not None:
my_open_orders_for_this_market.append(my_lowest_sell)
if my_highest_buy is not None:
my_open_orders_for_this_market.append(my_highest_buy)
my_open_orders[market] = my_open_orders_for_this_market
return my_open_orders
# The spread_pair is the recommended buy/sell. If an open order exists, and within an EXISTING_INDICATOR_PERCENT from it,
# then the recommended spread is not required
def get_required_orders(spread_orders, my_open_orders, allow_multiple_spread_orders, factor): # factor is percentage to spread when my order already exists
required_orders = []
for spread_pair, spread_order in spread_orders.items():
print('\n\n[get_required_orders] {}: {}, factor = {}'.format(spread_pair, spread_order, factor))
print('=========================================================================================================')
# Need to normalize the sell/buy to yield the same NIS value:
# Consider: Sell at 40, buy at 15. So normalize sell *AMOUNT* by 15/40
if spread_pair in list(my_open_orders.keys()):
print("[get_required_orders] {} exists in my_open_orders".format(spread_pair))
added_sell = False
added_buy = False
for my_open_order in my_open_orders[spread_pair]:
print("[get_required_orders] Checking my_open_order: {:4} {:10} at {:8}".format(my_open_order['side'], my_open_order['amount'], my_open_order['price']), end='')
if not added_sell and my_open_order['side'] == 'sell' and (abs(my_open_order['price']-spread_order['sell']['at'])/my_open_order['price'] > EXISTING_INDICATOR_PERCENT/100 or allow_multiple_spread_orders):
added_sell = True
if MODE != BUY_ONLY:
order = {
'pair': spread_pair,
'side': 'sell',
'price': round(spread_order['sell']['at']*(1.0 - float(factor)/100),4),
'normalization_factor': spread_order['buy']['at']/spread_order['sell']['at']
}
print(' Adding sell order: price={:8}'.format(order['price']))
required_orders.append(order)
if not added_sell: print('\n')
if not added_buy and my_open_order['side'] == 'buy' and (abs(my_open_order['price']-spread_order['buy']['at'])/my_open_order['price'] > EXISTING_INDICATOR_PERCENT/100 or allow_multiple_spread_orders):
added_buy = True
order = {
'pair': spread_pair,
'side': 'buy',
'price': round(spread_order['buy']['at']*(1.0 + float(factor)/100),4),
'normalization_factor': 1.0
}
print(' Adding buy order: price={:8}'.format(order['price']))
required_orders.append(order)
if not added_buy and not added_sell: print('\n')
else:
print("[get_required_orders] {} does not exist in my_open_orders".format(spread_pair))
print('[get_required_orders] Recommended order: {}'.format(spread_order))
if MODE != BUY_ONLY:
order_sell = {
'pair' : spread_pair,
'side' : 'sell',
'price': spread_order['sell']['at'],
'normalization_factor': spread_order['buy']['at']/spread_order['sell']['at']
}
required_orders.append(order_sell)
order_buy = {
'pair' : spread_pair,
'side' : 'buy',
'price': spread_order['buy']['at'],
'normalization_factor': 1.0
}
required_orders.append(order_buy)
print("\n")
return required_orders
def create_priced_orders(required_orders, balances):
priced_orders = []
print('[create_priced_orders] ORDER_PERCENTAGE = {}%'.format(ORDER_PERCENTAGE))
current_currency = None
for required_order in required_orders:
currency = required_order['pair'].replace('/NIS', '')
available_str = 'AVAILABLE_' + currency.replace('BCH','BCHABC').replace('BSV', 'BCHSV')
if currency != current_currency:
print('[create_priced_orders] {} = {}'.format(available_str, balances['data']['info'][available_str]))
current_currency = currency
print('[create_priced_orders] required_order: {:4} @ {:8} NIS using normalization_factor of {:4} -> '.format(required_order['side'], required_order['price'], round(required_order['normalization_factor'],2)),end='')
if required_order['side'] == 'sell' and balances['data']['info'][available_str] > 0:
amount = round(balances['data']['info'][available_str]*ORDER_PERCENTAGE/100,2)*required_order['normalization_factor']
if amount*required_order['price'] < MINIMUM_ORDER_NIS:
amount = round(MINIMUM_ORDER_NIS/required_order['price'],2)
priced_order = {
'pair' : required_order['pair'],
'side' : 'sell',
'amount': amount,
'price' : required_order['price'],
'volume': amount * required_order['price']
}
print('Added: amount={:10}, volume={:8}'.format(round(priced_order['amount'],8), round(priced_order['volume'],2)))
priced_orders.append(priced_order)
elif required_order['side'] == 'buy' and balances['data']['info'][available_str] > 0:
amount = round(balances['data']['info'][available_str]*ORDER_PERCENTAGE/100,2)*required_order['normalization_factor']
if amount*required_order['price'] < MINIMUM_ORDER_NIS:
amount = round(MINIMUM_ORDER_NIS/required_order['price'],2)
priced_order = {
'pair' : required_order['pair'],
'side' : 'buy',
'amount': amount,
'price' : required_order['price'],
'volume': amount*required_order['price']
}
print('Added: amount={:10}, volume={:8}'.format(round(priced_order['amount'],8), round(priced_order['volume'],2)))
priced_orders.append(priced_order)
else:
print('Not Added\n')
print("\n")
return priced_orders
def execute_priced_orders(exchange, priced_orders):
for order in priced_orders:
print('[execute_priced_orders] placing order {}'.format(order))
try:
if order['side'] == 'sell':
exchange.create_limit_sell_order(order['pair'], order['amount'], order['price'])
elif order['side'] == 'buy':
exchange.create_limit_buy_order(order['pair'], order['amount'], order['price'])
except:
print('[execute_priced_orders] FAILED placing order {}'.format(order))
def main():
print("\n")
exchange = connect_to_bit2c(api=API_1) # False/True for debug Plot
iteration = 0
num_of_my_previous_required_orders = 0
while True:
iteration += 1
balances = {}
get_balances(plot=(GRAPH_ONLY or RUN_ONCE or VISUAL_MODE), exchange=exchange, balances=balances) # False/True for debug Plot
if GRAPH_ONLY:
if RUN_ONCE:
return
else:
continue
markets = exchange.load_markets()
if iteration == 1 and CANCEL_ALL_ORDERS:
cancel_my_open_orders(exchange, markets)
if MODE == CANCEL_ALL:
return
print('[main] Sleep a little to let the other traders calm down')
time.sleep(5) # Sleep a little to let the other traders calm down
spread_orders = bit2c_classic_margins(endless_mode=False)
my_open_orders = scan_my_open_orders(exchange, markets)
num_of_my_open_orders = 0
for market in my_open_orders:
print('\n [main] My {} Open Orders:'.format(market))
for order in my_open_orders[market]:
num_of_my_open_orders += 1
print(' [main] {:5} {:11} at {:7} (Volume is {:5}, Total orders of {})'.format(order['side'], order['amount'], order['price'], round(order['amount']*order['price'],2), num_of_my_open_orders))
required_orders = get_required_orders(spread_orders=spread_orders, my_open_orders=my_open_orders, allow_multiple_spread_orders=True, factor=(iteration-1)*SPREAD_ADVANCE_FACTOR)
if INFORMATIVE_ONLY is False and iteration > NUM_ITERATIONS_AFTER_WHICH_TO_CHECK_SANITY:
if len(required_orders) == num_of_my_previous_required_orders:
print('[main] No change in len(required_orders)={} from last iteration, stopping run'.format(len(required_orders))) # TODO: ASFAR: Can reset iteration to 0 for instance instead of exiting
return
num_of_my_previous_required_orders = len(required_orders)
priced_orders = create_priced_orders(required_orders, balances)
if INFORMATIVE_ONLY:
if RUN_ONCE:
return
else:
continue
execute_priced_orders(exchange, priced_orders)
print('[main] Completed iteration {}'.format(iteration))
if RUN_ONCE:
return
time.sleep(1)
if __name__ == '__main__':
main()