· 5 years ago · Jun 29, 2020, 10:58 PM
1"""
2 Подробная информация о боте на сайте bablofil.ru/bot-dlya-binance
3"""
4import sqlite3
5import logging
6import time
7import os
8
9from datetime import datetime
10
11from binance_api import Binance
12bot = Binance(
13 API_KEY='EEt5ipvr7JMMn1CSO6alvf6lEKk4Dmz05WOX0hb7TcaBWMqn9evA8PeTjx7e4Ewe',
14 API_SECRET='xQpOoyHTWm8jRc5wK4CvgsThF3JNxvGgj987meB7rmPaSUIKJsxLhUTTalrHqpUz'
15)
16
17perc = 0.002
18
19"""
20 Пропишите пары, на которые будет идти торговля.
21 base - это базовая пара (BTC, ETH, BNB, USDT) - то, что на бинансе пишется в табличке сверху
22 quote - это квотируемая валюта. Например, для торгов по паре NEO/USDT базовая валюта USDT, NEO - квотируемая
23"""
24
25
26pairs = [
27 {
28 'base': 'USDT',
29 'quote': 'BTC',
30 'offers_amount': 10, # Сколько предложений из стакана берем для расчета средней цены
31 # Максимум 1000. Допускаются следующие значения:[5, 10, 20, 50, 100, 500, 1000]
32 'spend_sum': 20, # Сколько тратить base каждый раз при покупке quote
33 'profit_markup': 0.0015, # Какой навар нужен с каждой сделки? (0.001 = 0.1%)
34 'use_stop_loss': False, # Нужно ли продавать с убытком при падении цены
35 'stop_loss': 2, # 2% - На сколько должна упасть цена, что бы продавать с убытком
36
37 }
38]
39
40
41
42BUY_LIFE_TIME_SEC = 300 # Сколько (в секундах) держать ордер на продажу открытым
43
44STOCK_FEE = 0.00075 # Комиссия, которую берет биржа (0.001 = 0.1%)
45
46# Если вы решите не платить комиссию в BNB, то установите в False. Обычно делать этого не надо
47USE_BNB_FEES = True
48
49# Получаем ограничения торгов по всем парам с биржи
50local_time = int(time.time())
51limits = bot.exchangeInfo()
52server_time = int(limits['serverTime'])//1000
53
54# Ф-ция, которая приводит любое число к числу, кратному шагу, указанному биржей
55# Если передать параметр increase=True то округление произойдет к следующему шагу
56def adjust_to_step(value, step, increase=False):
57 return ((int(value * 100000000) - int(value * 100000000) % int(
58 float(step) * 100000000)) / 100000000)+(float(step) if increase else 0)
59
60# Подключаем логирование
61logging.basicConfig(
62 format="%(asctime)s [%(levelname)-5.5s] %(message)s",
63 level=logging.DEBUG,
64 handlers=[
65 logging.FileHandler("{path}/logs/{fname}.log".format(path=os.path.dirname(os.path.abspath(__file__)), fname="binance")),
66 logging.StreamHandler()
67 ])
68
69log = logging.getLogger('')
70
71# Бесконечный цикл программы
72
73shift_seconds = server_time-local_time
74bot.set_shift_seconds(shift_seconds)
75
76log.debug("""
77 Текущее время: {local_time_d} {local_time_u}
78 Время сервера: {server_time_d} {server_time_u}
79 Разница: {diff:0.8f} {warn}
80 Бот будет работать, как будто сейчас: {fake_time_d} {fake_time_u}
81""".format(
82 local_time_d = datetime.fromtimestamp(local_time), local_time_u=local_time,
83 server_time_d=datetime.fromtimestamp(server_time), server_time_u=server_time,
84 diff=abs(local_time-server_time),
85 warn="ТЕКУЩЕЕ ВРЕМЯ ВЫШЕ" if local_time > server_time else '',
86 fake_time_d=datetime.fromtimestamp(local_time+shift_seconds), fake_time_u=local_time+shift_seconds
87))
88
89while True:
90 try:
91 # Устанавливаем соединение с локальной базой данных
92 conn = sqlite3.connect('binance.db')
93 cursor = conn.cursor()
94
95 # Если не существует таблиц, их нужно создать (первый запуск)
96 orders_q = """
97 create table if not exists
98 orders (
99 order_type TEXT,
100 order_pair TEXT,
101
102 buy_order_id NUMERIC,
103 buy_amount REAL,
104 buy_price REAL,
105 buy_created DATETIME,
106 buy_finished DATETIME NULL,
107 buy_cancelled DATETIME NULL,
108
109 sell_order_id NUMERIC NULL,
110 sell_amount REAL NULL,
111 sell_price REAL NULL,
112 sell_created DATETIME NULL,
113 sell_finished DATETIME NULL,
114 force_sell INT DEFAULT 0
115 );
116 """
117 cursor.execute(orders_q)
118
119 log.debug("Получаем все неисполненные ордера по БД")
120
121 orders_q = """
122 SELECT
123 CASE WHEN order_type='buy' THEN buy_order_id ELSE sell_order_id END order_id
124 , order_type
125 , order_pair
126 , sell_amount
127 , sell_price
128 , strftime('%s',buy_created)
129 , buy_amount
130 , buy_price
131 FROM
132 orders
133 WHERE
134 buy_cancelled IS NULL AND CASE WHEN order_type='buy' THEN buy_finished IS NULL ELSE sell_finished IS NULL END
135 """
136 orders_info = {}
137
138
139 for row in cursor.execute(orders_q):
140 orders_info[str(row[0])] = {'order_type': row[1], 'order_pair': row[2], 'sell_amount': row[3], 'sell_price': row[4],
141 'buy_created': row[5], 'buy_amount': row[6], 'buy_price': row[7] }
142 # формируем словарь из указанных пар, для удобного доступа
143 all_pairs = {pair['quote'].upper() + pair['base'].upper():pair for pair in pairs}
144
145 if orders_info:
146 log.debug("Получены неисполненные ордера из БД: {orders}".format(orders=[(order, orders_info[order]['order_pair']) for order in orders_info]))
147
148 # Проверяем каждый неисполненный по базе ордер
149 for order in orders_info:
150 # Получаем по ордеру последнюю информацию по бирже
151 stock_order_data = bot.orderInfo(symbol=orders_info[order]['order_pair'], orderId=order)
152
153 order_status = stock_order_data['status']
154 log.debug("Состояние ордера {order} - {status}".format(order=order, status=order_status))
155 if order_status == 'NEW':
156 log.debug('Ордер {order} всё еще не выполнен'.format(order=order))
157
158 # Если ордер на покупку
159 if orders_info[order]['order_type'] == 'buy':
160 # Если ордер уже исполнен
161 if order_status == 'FILLED':
162 log.info("""
163 Ордер {order} выполнен, получено {exec_qty:0.8f}.
164 Создаем ордер на продажу
165 """.format(
166 order=order, exec_qty=float(stock_order_data['executedQty'])
167 ))
168
169 # смотрим, какие ограничения есть для создания ордера на продажу
170 for elem in limits['symbols']:
171 if elem['symbol'] == orders_info[order]['order_pair']:
172 CURR_LIMITS = elem
173 break
174 else:
175 raise Exception("Не удалось найти настройки выбранной пары " + pair_name)
176
177 # Рассчитываем данные для ордера на продажу
178
179 # Имеющееся кол-во на продажу
180 has_amount = orders_info[order]['buy_amount']*((1-STOCK_FEE) if not USE_BNB_FEES else 1)
181 # Приводим количество на продажу к числу, кратному по ограничению
182 sell_amount = adjust_to_step(has_amount, CURR_LIMITS['filters'][2]['stepSize'])
183 # Рассчитываем минимальную сумму, которую нужно получить, что бы остаться в плюсе
184 need_to_earn = orders_info[order]['buy_amount']*orders_info[order]['buy_price']*(1+all_pairs[stock_order_data['symbol']]['profit_markup'])
185 # Рассчитываем минимальную цену для продажи
186 min_price = (need_to_earn/sell_amount)/((1-STOCK_FEE) if not USE_BNB_FEES else 1)
187 # Приводим к нужному виду, если цена после срезки лишних символов меньше нужной, увеличиваем на шаг
188 cut_price = max(
189 adjust_to_step(min_price, CURR_LIMITS['filters'][0]['tickSize'], increase=True),
190 adjust_to_step(min_price, CURR_LIMITS['filters'][0]['tickSize'])
191 )
192 # Получаем текущие курсы с биржи
193 curr_rate = float(bot.tickerPrice(symbol=orders_info[order]['order_pair'])['price'])
194 # Если текущая цена выше нужной, продаем по текущей
195 need_price = max(cut_price, curr_rate)
196
197 log.info("""
198 Изначально было куплено {buy_initial:0.8f}, за вычетом комиссии {has_amount:0.8f},
199 Получится продать только {sell_amount:0.8f}
200 Нужно получить как минимум {need_to_earn:0.8f} {curr}
201 Мин. цена (с комиссией) составит {min_price}, после приведения {cut_price:0.8f}
202 Текущая цена рынка {curr_rate:0.8f}
203 Итоговая цена продажи: {need_price:0.8f}
204 """.format(
205 buy_initial=orders_info[order]['buy_amount'], has_amount=has_amount,sell_amount=sell_amount,
206 need_to_earn=need_to_earn, curr=all_pairs[orders_info[order]['order_pair']]['base'],
207 min_price=min_price, cut_price=cut_price, need_price=need_price,
208 curr_rate=curr_rate
209 ))
210
211 # Если итоговая сумма продажи меньше минимума, ругаемся и не продаем
212 if (need_price*has_amount) <float(CURR_LIMITS['filters'][3]['minNotional']):
213 raise Exception("""
214 Итоговый размер сделки {trade_am:0.8f} меньше допустимого по паре {min_am:0.8f}. """.format(
215 trade_am=(need_price*has_amount), min_am=float(CURR_LIMITS['filters'][3]['minNotional'])
216 ))
217
218 log.debug(
219 'Рассчитан ордер на продажу: кол-во {amount:0.8f}, курс: {rate:0.8f}'.format(
220 amount=sell_amount, rate=need_price)
221 )
222
223 # Отправляем команду на создание ордера с рассчитанными параметрами
224 new_order = bot.createOrder(
225 symbol=orders_info[order]['order_pair'],
226 recvWindow=5000,
227 side='SELL',
228 type='LIMIT',
229 timeInForce='GTC', # Good Till Cancel
230 quantity="{quantity:0.{precision}f}".format(
231 quantity=sell_amount, precision=CURR_LIMITS['baseAssetPrecision']
232 ),
233 price="{price:0.{precision}f}".format(
234 price=need_price, precision=CURR_LIMITS['baseAssetPrecision']
235 ),
236 newOrderRespType='FULL'
237 )
238 # Если ордер создался без ошибок, записываем данные в базу данных
239 if 'orderId' in new_order:
240 log.info("Создан ордер на продажу {new_order}".format(new_order=new_order))
241 cursor.execute(
242 """
243 UPDATE orders
244 SET
245 order_type = 'sell',
246 buy_finished = datetime(),
247 sell_order_id = :sell_order_id,
248 sell_created = datetime(),
249 sell_amount = :sell_amount,
250 sell_price = :sell_initial_price
251 WHERE
252 buy_order_id = :buy_order_id
253
254 """, {
255 'buy_order_id': order,
256 'sell_order_id': new_order['orderId'],
257 'sell_amount': sell_amount,
258 'sell_initial_price': need_price
259 }
260 )
261 conn.commit()
262 # Если были ошибки при создании, выводим сообщение
263 else:
264 log.warning("Не удалось создать ордер на продажу {new_order}".format(new_order=new_order))
265
266 # Ордер еще не исполнен, частичного исполнения нет, проверяем возможность отмены
267 elif order_status == 'NEW':
268 order_created = int(orders_info[order]['buy_created'])
269 time_passed = int(time.time()) - order_created
270 log.debug("Прошло времени после создания {passed:0.2f}".format(passed=time_passed))
271 # Прошло больше времени, чем разрешено держать ордер
272 if time_passed > BUY_LIFE_TIME_SEC:
273 log.info("""Ордер {order} пора отменять, прошло {passed:0.1f} сек.""".format(
274 order=order, passed=time_passed
275 ))
276 # Отменяем ордер на бирже
277 cancel = bot.cancelOrder(
278 symbol=orders_info[order]['order_pair'],
279 orderId=order
280 )
281 # Если удалось отменить ордер, скидываем информацию в БД
282 if 'orderId' in cancel:
283
284 log.info("Ордер {order} был успешно отменен".format(order=order))
285 cursor.execute(
286 """
287 UPDATE orders
288 SET
289 buy_cancelled = datetime()
290 WHERE
291 buy_order_id = :buy_order_id
292 """, {
293 'buy_order_id': order
294 }
295 )
296
297 conn.commit()
298 else:
299 log.warning("Не удалось отменить ордер: {cancel}".format(cancel=cancel))
300 elif order_status == 'PARTIALLY_FILLED':
301 log.debug("Ордер {order} частично исполнен, ждем завершения".format(order=order))
302
303 # Если это ордер на продажу, и он исполнен
304 if order_status == 'FILLED' and orders_info[order]['order_type'] == 'sell':
305 log.debug("Ордер {order} на продажу исполнен".format(
306 order=order
307 ))
308 # Обновляем информацию в БД
309 cursor.execute(
310 """
311 UPDATE orders
312 SET
313 sell_finished = datetime()
314 WHERE
315 sell_order_id = :sell_order_id
316
317 """, {
318 'sell_order_id': order
319 }
320 )
321 conn.commit()
322 if all_pairs[orders_info[order]['order_pair']]['use_stop_loss']:
323
324 if order_status == 'NEW' and orders_info[order]['order_type'] == 'sell':
325 curr_rate = float(bot.tickerPrice(symbol=orders_info[order]['order_pair'])['price'])
326
327 if (1 - curr_rate/orders_info[order]['buy_price'])*100 >= all_pairs[orders_info[order]['order_pair']]['stop_loss']:
328 log.debug("{pair} Цена упала до стоплосс (покупали по {b:0.8f}, сейчас {s:0.8f}), пора продавать".format(
329 pair=orders_info[order]['order_pair'],
330 b=orders_info[order]['buy_price'],
331 s=curr_rate
332 ))
333 # Отменяем ордер на бирже
334 cancel = bot.cancelOrder(
335 symbol=orders_info[order]['order_pair'],
336 orderId=order
337 )
338 # Если удалось отменить ордер, скидываем информацию в БД
339 if 'orderId' in cancel:
340 log.info("Ордер {order} был успешно отменен, продаем по рынку".format(order=order))
341 new_order = bot.createOrder(
342 symbol=orders_info[order]['order_pair'],
343 recvWindow=15000,
344 side='SELL',
345 type='MARKET',
346 quantity=orders_info[order]['sell_amount'],
347 )
348 if not new_order.get('code'):
349 log.info("Создан ордер на продажу по рынку " + str(new_order))
350 cursor.execute(
351 """
352 DELETE FROM orders
353 WHERE
354 sell_order_id = :sell_order_id
355 """, {
356 'sell_order_id': order
357 }
358 )
359 conn.commit()
360 else:
361 log.warning("Не удалось отменить ордер: {cancel}".format(cancel=cancel))
362 else:
363 log.debug("{pair} (покупали по {b:0.8f}, сейчас {s:0.8f}), расхождение {sl:0.4f}%, panic_sell = {ps:0.4f}% ({ps_rate:0.8f}), продажа с профитом: {tp:0.8f}".format(
364 pair=orders_info[order]['order_pair'],
365 b=orders_info[order]['buy_price'],
366 s=curr_rate,
367 sl=(1 - curr_rate/orders_info[order]['buy_price'])*100,
368 ps=all_pairs[orders_info[order]['order_pair']]['stop_loss'],
369 ps_rate=orders_info[order]['buy_price']/100 * (100-all_pairs[orders_info[order]['order_pair']]['stop_loss']),
370 tp=orders_info[order]['sell_price']
371 ))
372
373 elif order_status == 'CANCELED' and orders_info[order]['order_type'] == 'sell':
374 # На случай, если после отмены произошел разрыв связи
375 new_order = bot.createOrder(
376 symbol=orders_info[order]['order_pair'],
377 recvWindow=15000,
378 side='SELL',
379 type='MARKET',
380 quantity=orders_info[order]['sell_amount'],
381 )
382 if not new_order.get('code'):
383 log.info("Создан ордер на продажу по рынку " + str(new_order))
384 cursor.execute(
385 """
386 DELETE FROM orders
387 WHERE
388 sell_order_id = :sell_order_id
389 """, {
390 'sell_order_id': order
391 }
392 )
393 conn.commit()
394 else:
395 log.debug("Неисполненных ордеров в БД нет")
396
397 log.debug('Получаем из настроек все пары, по которым нет неисполненных ордеров')
398
399 orders_q = """
400 SELECT
401 distinct(order_pair) pair
402 FROM
403 orders
404 WHERE
405 buy_cancelled IS NULL AND CASE WHEN order_type='buy' THEN buy_finished IS NULL ELSE sell_finished IS NULL END
406 """
407 # Получаем из базы все ордера, по которым есть торги, и исключаем их из списка, по которому будем создавать новые ордера
408 for row in cursor.execute(orders_q):
409 del all_pairs[row[0]]
410
411 # Если остались пары, по которым нет текущих торгов
412 if all_pairs:
413 log.debug('Найдены пары, по которым нет неисполненных ордеров: {pairs}'.format(pairs=list(all_pairs.keys())))
414 for pair_name, pair_obj in all_pairs.items():
415 log.debug("Работаем с парой {pair}".format(pair=pair_name))
416
417 # Получаем лимиты пары с биржи
418 for elem in limits['symbols']:
419 if elem['symbol'] == pair_name:
420 CURR_LIMITS = elem
421 break
422 else:
423 raise Exception("Не удалось найти настройки выбранной пары " + pair_name)
424
425 # Получаем балансы с биржи по указанным валютам
426 balances = {
427 balance['asset']: float(balance['free']) for balance in bot.account()['balances']
428 if balance['asset'] in [pair_obj['base'], pair_obj['quote']]
429 }
430 log.debug("Баланс {balance}".format(balance=["{k}:{bal:0.8f}".format(k=k, bal=balances[k]) for k in balances]))
431 # Если баланс позволяет торговать - выше лимитов биржи и выше указанной суммы в настройках
432 if balances[pair_obj['base']] >= pair_obj['spend_sum']:
433 # Получаем информацию по предложениям из стакана, в кол-ве указанном в настройках
434 offers = bot.depth(
435 symbol=pair_name,
436 limit=pair_obj['offers_amount']
437 )
438
439
440 # Берем цены покупок (для цен продаж замените bids на asks)
441 prices = [float(bid[0]) for bid in offers['bids']]
442 #print("Hello, World!")
443 # print prices
444 try:
445 # Рассчитываем среднюю цену из полученных цен
446 # avg_price = sum(prices) / len(prices)
447 # perc = 0.01
448 avg_price = prices[0] * (1- perc)
449 # Среднюю цену приводим к требованиям биржи о кратности
450 my_need_price = adjust_to_step(avg_price, CURR_LIMITS['filters'][0]['tickSize'])
451 # Рассчитываем кол-во, которое можно купить, и тоже приводим его к кратному значению
452 my_amount = adjust_to_step(pair_obj['spend_sum']/ my_need_price, CURR_LIMITS['filters'][2]['stepSize'])
453 # Если в итоге получается объем торгов меньше минимально разрешенного, то ругаемся и не создаем ордер
454 if my_amount < float(CURR_LIMITS['filters'][2]['stepSize']) or my_amount < float(CURR_LIMITS['filters'][2]['minQty']):
455 log.warning("""
456 Минимальная сумма лота: {min_lot:0.8f}
457 Минимальный шаг лота: {min_lot_step:0.8f}
458 На свои деньги мы могли бы купить {wanted_amount:0.8f}
459 После приведения к минимальному шагу мы можем купить {my_amount:0.8f}
460 Покупка невозможна, выход. Увеличьте размер ставки
461 """.format(
462 wanted_amount=pair_obj['spend_sum']/ my_need_price,
463 my_amount=my_amount,
464 min_lot=float(CURR_LIMITS['filters'][2]['minQty']),
465 min_lot_step=float(CURR_LIMITS['filters'][2]['stepSize'])
466 ))
467 continue
468
469 # Итоговый размер лота
470 trade_am = my_need_price*my_amount
471 log.debug("""
472 Средняя цена {av_price:0.8f},
473 после приведения {need_price:0.8f},
474 объем после приведения {my_amount:0.8f},
475 итоговый размер сделки {trade_am:0.8f}
476 """.format(
477 av_price=avg_price, need_price=my_need_price, my_amount=my_amount, trade_am=trade_am
478 ))
479 # Если итоговый размер лота меньше минимального разрешенного, то ругаемся и не создаем ордер
480 if trade_am < float(CURR_LIMITS['filters'][3]['minNotional']):
481 raise Exception("""
482 Итоговый размер сделки {trade_am:0.8f} меньше допустимого по паре {min_am:0.8f}.
483 Увеличьте сумму торгов (в {incr} раз(а))""".format(
484 trade_am=trade_am, min_am=float(CURR_LIMITS['filters'][3]['minNotional']),
485 incr=float(CURR_LIMITS['filters'][3]['minNotional'])/trade_am
486 ))
487 log.debug(
488 'Рассчитан ордер на покупку: кол-во {amount:0.8f}, курс: {rate:0.8f}'.format(amount=my_amount, rate=my_need_price)
489 )
490 # Отправляем команду на бирже о создании ордера на покупку с рассчитанными параметрами
491 new_order = bot.createOrder(
492 symbol=pair_name,
493 recvWindow=5000,
494 side='BUY',
495 type='LIMIT',
496 timeInForce='GTC', # Good Till Cancel
497 quantity="{quantity:0.{precision}f}".format(
498 quantity=my_amount, precision=CURR_LIMITS['baseAssetPrecision']
499 ),
500 price="{price:0.{precision}f}".format(
501 price=my_need_price, precision=CURR_LIMITS['baseAssetPrecision']
502 ),
503 newOrderRespType='FULL'
504 )
505 # Если удалось создать ордер на покупку, записываем информацию в БД
506 if 'orderId' in new_order:
507 log.info("Создан ордер на покупку {new_order}".format(new_order=new_order))
508 cursor.execute(
509 """
510 INSERT INTO orders(
511 order_type,
512 order_pair,
513 buy_order_id,
514 buy_amount,
515 buy_price,
516 buy_created
517
518 ) Values (
519 'buy',
520 :order_pair,
521 :order_id,
522 :buy_order_amount,
523 :buy_initial_price,
524 datetime()
525 )
526 """, {
527 'order_pair': pair_name,
528 'order_id': new_order['orderId'],
529 'buy_order_amount': my_amount,
530 'buy_initial_price': my_need_price
531 }
532 )
533 conn.commit()
534 else:
535 log.warning("Не удалось создать ордер на покупку! {new_order}".format(new_order=str(new_order)))
536
537 except ZeroDivisionError:
538 log.debug('Не удается вычислить среднюю цену: {prices}'.format(prices=str(prices)))
539 else:
540 log.warning('Для создания ордера на покупку нужно минимум {min_qty:0.8f} {curr}, выход'.format(
541 min_qty=pair_obj['spend_sum'], curr=pair_obj['base']
542 ))
543
544 else:
545 log.debug('По всем парам есть неисполненные ордера')
546
547 except Exception as e:
548 log.exception(e)
549 finally:
550 conn.close()