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