· 8 years ago · Apr 19, 2018, 01:14 PM
1import hashlib
2import hmac
3import http.client
4import json
5import sqlite3
6import sys
7import time
8import urllib
9from statistics import median
10from urllib.parse import urlparse
11from datetime import datetime
12
13# Ð”Ð»Ñ Ð½Ð°Ñтройки бота измените Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ ÑоответÑтвующих конÑтант
14
15# ############ ПользовательÑÐºÐ°Ñ ÐºÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ #############
16# Ключи API, которые предоÑтавил Poloniex
17API_KEY = 'PFW70J4E-YNZR0RTN-02UMVYHS-ZBI2OXF3'
18# Обратите внимание, что добавлена 'b' перед Ñтрокой
19API_SECRET = b'6fc319923409464d30ad0f442f8b478a3c2878df0fd023f774cb5b32b4d81d5669c441c9ab2e8f6e6b0eadd0c1b047a4299d4177033a9669e3347107b3c5cde9'
20
21# Ð’Ð°Ð»ÑŽÑ‚Ð½Ð°Ñ Ð¿Ð°Ñ€Ð°, по которой ÑобираетеÑÑŒ торговать
22CURRENCY_1 = 'USDT'
23CURRENCY_2 = 'BTC'
24
25# Сколько тратить CURRENCY_2 каждый раз при покупке CURRENCY_1
26CAN_SPEND = 0.003 # 24.51 USDT
27
28# Какой навар нужен Ñ ÐºÐ°Ð¶Ð´Ð¾Ð¹ Ñделки при покупке (поверх комиÑÑии)? Можно поÑтавить 0
29PROFIT_MARKUP_DOWN = 0.001 # .001 = .1%
30# Какой навар нужен Ñ ÐºÐ°Ð¶Ð´Ð¾Ð¹ Ñделки при продаже (поверх комиÑÑии)?
31PROFIT_MARKUP_UP = 0.002 # .002 = .2%
32# ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ ÐºÐ¾Ð¼Ð¸ÑÑиÑ, которую берет биржа
33STOCK_FEE = 0.0015 # .002 = .2%
34
35# Сколько предложений брать из Ñтакана Ð´Ð»Ñ Ñ€Ð°ÑÑчета Ñредней цены
36OFFERS_AMOUNT = 3
37
38# Через Ñколько минут отменÑть неиÑполненный ордер на покупку валюты 1
39ORDER_LIFE_TIME = 3
40
41# True - выводить отладочную информацию, False - пиÑать как можно меньше
42DEBUG = True
43
44# ############ Конец конфигурации #############
45
46CURR_PAIR = CURRENCY_1 + '_' + CURRENCY_2
47
48
49# КлаÑÑÑ‹ Ð´Ð»Ñ Ð¾Ñ‚ÑÐ»ÐµÐ¶Ð¸Ð²Ð°Ð½Ð¸Ñ ÑобÑтвенных иÑключений
50class ScriptError(Exception):
51 pass
52
53
54class ScriptQuitCondition(Exception):
55 pass
56
57
58def log(*args):
59 print(datetime.now(), *args)
60
61
62# Обращение к Poloniex API (по умолчанию выÑтовлен Trading API URL)
63def call_api(api_url='https://poloniex.com/tradingApi', http_method="POST", **kwargs):
64 time.sleep(0.2) # По правилам биржи Ð½ÐµÐ»ÑŒÐ·Ñ Ð±Ð¾Ð»ÑŒÑˆÐµ 6 запроÑов в Ñекунду
65 payload = {'nonce': int(round(time.time() * 1000))}
66
67 if kwargs:
68 payload.update(kwargs)
69 payload = urllib.parse.urlencode(payload)
70
71 hmac_impl = hmac.new(key=API_SECRET, digestmod=hashlib.sha512)
72 hmac_impl.update(payload.encode('utf-8'))
73 sign = hmac_impl.hexdigest()
74
75 headers = {"Content-type": "application/x-www-form-urlencoded",
76 "Key": API_KEY,
77 "Sign": sign}
78
79 url_o = urlparse(api_url)
80 conn = http.client.HTTPSConnection(url_o.netloc)
81 conn.request(http_method, api_url, payload, headers)
82 response = conn.getresponse().read()
83
84 conn.close()
85
86 try:
87 obj = json.loads(response.decode('utf-8'))
88
89 if 'error' in obj and obj['error']:
90 raise ScriptError(obj['error'])
91 return obj
92 except ValueError: # json.decoder.JSONDecodeError
93 raise ScriptError('Ошибка анализа возвращаемых данных, получена Ñтрока', response)
94
95
96# ОÑÐ½Ð¾Ð²Ð½Ð°Ñ Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ, в которой пропиÑана логика работы бота
97def main_flow():
98 try:
99 ''' ПодключаемÑÑ Ðº локальной SQLite3 базе данных (local.db)
100 Ее иÑпользование необходимо из-за Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾Ñтей Poloniex API:
101 он не предоÑтавлÑет дату выÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¾Ñ€Ð´ÐµÑ€Ð¾Ð², а значит не предÑтавлÑетÑÑ
102 возможным контролировать неÑколько долго виÑит ордер, что неприемлемо длÑ
103 логики работы бота'''
104 conn = sqlite3.connect('local.db')
105 cursor = conn.cursor() # ИÑполнÑет запроÑÑ‹ к базе данных
106
107 # ЕÑли не ÑущеÑтвует таблиц, их нужно Ñоздать (при первом запуÑке)
108 orders_query = '''
109 create table if not exists
110 orders (
111 order_type TEXT,
112 order_pair TEXT,
113
114 buy_order_id NUMERIC,
115 buy_initial_amount REAL,
116 buy_initial_price REAL,
117 buy_created DATETIME,
118 buy_finished DATETIME,
119 buy_cancelled DATETIME,
120
121 sell_order_id NUMERIC,
122 sell_amount REAL,
123 sell_initial_price REAL,
124 sell_created DATETIME,
125 sell_finished DATETIME
126 );
127 '''
128 cursor.execute(orders_query)
129
130 if DEBUG:
131 log('Получаем вÑе неиÑполненные ордера по БД')
132
133 orders_q = '''
134 SELECT
135 CASE WHEN order_type='buy' THEN buy_order_id ELSE sell_order_id END order_id, order_type, sell_amount, sell_initial_price, strftime('%s',buy_created), buy_initial_price
136 FROM
137 orders
138 WHERE
139 buy_cancelled IS NULL AND CASE WHEN order_type='buy' THEN buy_finished IS NULL ELSE sell_finished IS NULL END AND order_pair LIKE ?
140 '''
141 # Чтобы избежать возможных SQL инъекций, будем передавать параметр через плейÑхолдер и кортеж
142 db_request = cursor.execute(orders_q, (CURR_PAIR,))
143 orders_info = {}
144 for row in db_request:
145 order_id = str(row[0])
146 orders_info[order_id] = {'order_type': row[1], 'sell_amount': row[2],
147 'sell_initial_price': row[3], 'buy_created': row[4], 'curr_rate': row[5]}
148 if orders_info:
149 if DEBUG:
150 log('Получены неиÑполненные ордера из БД:', [order_id for order_id in orders_info])
151
152 # Получаем информацию об открытых ордерах через Trading API
153 stock_orders = call_api(command='returnOpenOrders', currencyPair=CURR_PAIR)
154
155 if DEBUG:
156 log('Получена Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¿Ð¾ ордерам Ñ Ð±Ð¸Ñ€Ð¶Ð¸',
157 [order for order in stock_orders if len(stock_orders)])
158 for order_id in orders_info:
159 finished = True
160 for stock_order in stock_orders:
161 if stock_order['orderNumber'] == order_id:
162 finished = False
163 if DEBUG:
164 log('Ордер %s вÑÑ‘ еще не выполнен' % order_id)
165 break
166
167 if orders_info[order_id]['order_type'] == 'buy':
168 if finished:
169 log('Ордер на покупку %s выполнен' % order_id)
170 balance_left = float(call_api(command='returnBalances').get(CURRENCY_2, 0))
171 # if balance_left >= CAN_SPEND:
172 order_trades = call_api(command='returnOrderTrades', orderNumber=order_id)
173
174 got_amount = 0
175 max_fee = STOCK_FEE
176
177 for trade in order_trades:
178 got_amount += float(trade['amount']) - float(trade['amount']) * float(trade['fee'])
179 max_fee = max(max_fee, float(trade['fee']))
180
181 if max_fee != STOCK_FEE:
182 sell_price = float(orders_info[order_id]['curr_rate']) + float(
183 orders_info[order_id]['curr_rate']) * float(
184 max_fee + PROFIT_MARKUP_UP)
185 log(
186 'КомиÑÑÐ¸Ñ Ð½Ðµ Ñовпадает Ñ Ñ€Ð°Ñчетной, объем торгов изменилÑÑ. КомиÑÑиÑ: %s, объем: %s' % (
187 max_fee, got_amount))
188 new_order = call_api(command='sell', currencyPair=CURR_PAIR,
189 amount=got_amount, rate=sell_price)
190
191 # sell_amount = CAN_SPEND
192 sell_amount = got_amount
193 sell_initial_price = sell_price
194 else:
195 # ПытаемÑÑ Ñоздать ордер на продажу через Trading API
196 new_order = call_api(command='sell', currencyPair=CURR_PAIR,
197 amount=orders_info[order_id]['sell_amount'],
198 rate=orders_info[order_id]['sell_initial_price'])
199
200 # sell_amount = CAN_SPEND
201 sell_amount = orders_info[order_id]['sell_amount']
202 sell_initial_price = orders_info[order_id]['sell_initial_price']
203
204 if 'error' not in new_order:
205 log('Создан ордер на продажу', new_order)
206 cursor.execute(
207 '''
208 UPDATE orders
209 SET
210 order_type = 'sell',
211 buy_finished = datetime(),
212 sell_order_id = :sell_order_id,
213 sell_created = datetime(),
214 sell_amount = :sell_amount,
215 sell_initial_price = :sell_initial_price
216 WHERE
217 buy_order_id = :buy_order_id
218 ''', {
219 'buy_order_id': order_id,
220 'sell_order_id': new_order['orderNumber'],
221 'sell_amount': sell_amount,
222 'sell_initial_price': sell_initial_price
223 }
224 )
225 conn.commit()
226
227 else:
228 log('Ðе удалоÑÑŒ Ñоздать ордер на продажу', new_order)
229 # else:
230 # raise ScriptQuitCondition('Выход, не хватает денег')
231 else:
232 order_created = int(orders_info[order_id]['buy_created'])
233 time_passed = time.time() - order_created
234 if time_passed > ORDER_LIFE_TIME * 60:
235 log('Ордер по покупку не выполнен за %s Ñекунд, отменÑем' % time_passed)
236 # ПытаемÑÑ Ð¾Ñ‚Ð¼ÐµÐ½Ð¸Ñ‚ÑŒ ордер через Public API
237 cancel = call_api(command='cancelOrder', orderNumber=order_id)
238 if 'success' in cancel and cancel['success'] == 1:
239 log('Ордер %s был уÑпешно отменен' % order_id)
240 cursor.execute(
241 '''
242 UPDATE orders
243 SET
244 buy_cancelled = datetime()
245 WHERE
246 buy_order_id = :buy_order_id
247
248 ''', {
249 'buy_order_id': order_id
250 }
251 )
252 conn.commit()
253 else:
254 log('Какие-то проблемы при отмене ордера', cancel)
255
256 if finished and orders_info[order_id]['order_type'] == 'sell':
257 log('$' * 80)
258 log('#========== Ордер на продажу %s выполнен' % order_id)
259 # Узнаем Ñвой Ð±Ð°Ð»Ð°Ð½Ñ Ñ‡ÐµÑ€ÐµÐ· Trading API
260 current_balance = float(call_api(command='returnBalances').get(CURRENCY_1, 0))
261 log('Текущий баланÑ:', current_balance, CURRENCY_1)
262 log('$' * 80)
263 cursor.execute(
264 '''
265 UPDATE orders
266 SET
267 sell_finished = datetime()
268 WHERE
269 sell_order_id = :sell_order_id
270
271 ''', {
272 'sell_order_id': order_id
273 }
274 )
275 conn.commit()
276 elif DEBUG:
277 log('ÐеиÑполненных ордеров в БД нет')
278
279 if DEBUG:
280 log('Получаем из наÑтроек вÑе пары, по которым нет неиÑполненных ордеров')
281
282 orders_query = '''
283 SELECT
284 distinct(order_pair) pair
285 FROM
286 orders
287 WHERE
288 buy_cancelled IS NULL AND CASE WHEN order_type='buy' THEN buy_finished IS NULL ELSE sell_finished IS NULL END
289 '''
290 open_orders = False
291 for row in cursor.execute(orders_query):
292 if CURR_PAIR == row[0]:
293 open_orders = True
294 break
295
296 if not open_orders:
297 if DEBUG:
298 log('Ð’ текущей паре нет неиÑполненных ордеров')
299
300 # Получаем Ð¿Ñ€ÐµÐ´Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¸Ð· Ñтакана Ð´Ð»Ñ Ñ€Ð°ÑÑчета Ñредней цены через Public API
301 trades = call_api(api_url='https://poloniex.com/public?command=returnOrderBook¤cyPair=%s'
302 '&depth=%s' % (CURR_PAIR, OFFERS_AMOUNT))
303 if DEBUG:
304 log('Получено %s предложений из Ñтакана' % len(trades))
305
306 # Выбираем только лучшие из цен
307 buy_prices = [bid[0] for bid in trades['bids']]
308
309 # Делаем Ð·Ð°Ð¿Ñ€Ð¾Ñ Ðº Trading API, чтобы узнать Ñвой баланÑ
310 current_balances = call_api(command='returnBalances')
311 order_amount = float(current_balances.get(CURRENCY_1, 0))
312 if not buy_prices:
313 print('Ðе удалоÑÑŒ получить цены продаж из Ñтакана')
314 else:
315 # Ð’Ñ‹Ñчитываем Ñреднюю цену
316 buy_price = float(median(buy_prices))
317 curr_rate = buy_price + buy_price * float(STOCK_FEE - PROFIT_MARKUP_DOWN)
318 buy_amount = float(order_amount) / curr_rate
319 sell_amount = buy_amount - buy_amount * STOCK_FEE
320 sell_price = curr_rate + curr_rate * float(STOCK_FEE + PROFIT_MARKUP_UP)
321
322 print(
323 'ÐœÐµÐ´Ð¸Ð°Ð½Ð½Ð°Ñ Ñ†ÐµÐ½Ð° покупки = %0.8f, Ñ Ð½Ð°Ñ†ÐµÐ½ÐºÐ¾Ð¹ %s и комиÑÑией биржи %s ÐºÑƒÑ€Ñ ÑоÑтавит %0.8f'
324 %
325 (
326 buy_price,
327 PROFIT_MARKUP_DOWN,
328 STOCK_FEE,
329 curr_rate
330 )
331 )
332 print(
333 '''Итого ÑобираемÑÑ ÐºÑƒÐ¿Ð¸Ñ‚ÑŒ %0.8f %s по курÑу %0.8f.
334 ПоÑле вычета комиÑÑии оÑтанетÑÑ %0.8f %s, которые продадим по курÑу %0.8f.
335 Итого на Ð±Ð°Ð»Ð°Ð½Ñ ÑƒÐ¿Ð°Ð´ÐµÑ‚ %0.8f %s'''
336 %
337 (
338 buy_amount,
339 CURRENCY_2,
340 curr_rate,
341 sell_amount,
342 CURRENCY_2,
343 sell_price,
344 sell_amount * sell_price - buy_amount * curr_rate,
345 CURRENCY_1,
346 )
347 )
348
349 # ПытаемÑÑ Ð²Ñ‹Ñтавить ордер на покупку через Trading API
350 new_order = call_api(command='buy', currencyPair=CURR_PAIR, amount=buy_amount, rate=curr_rate)
351 if 'error' not in new_order:
352 print('Создан ордер на покупку', new_order)
353 cursor.execute(
354 '''
355 INSERT INTO orders(
356 order_type,
357 order_pair,
358 buy_order_id,
359 buy_initial_amount,
360 buy_initial_price,
361 buy_created,
362 sell_amount,
363 sell_initial_price
364 ) Values (
365 'buy',
366 :order_pair,
367 :order_id,
368 :buy_order_amount,
369 :buy_initial_price,
370 datetime(),
371 :sell_amount,
372 :sell_initial_price
373 )
374 ''', {
375 'order_pair': CURR_PAIR,
376 'order_id': new_order['orderNumber'],
377 'buy_order_amount': buy_amount,
378 'buy_initial_price': curr_rate,
379 'sell_amount': sell_amount,
380 'sell_initial_price': sell_price
381 }
382 )
383 conn.commit()
384 else:
385 print('Ðе удалоÑÑŒ Ñоздать ордер', new_order)
386
387 elif DEBUG:
388 print('ЕÑть еще неиÑполненные ордера')
389 except Exception as ex:
390 print(str(ex))
391 finally:
392 conn.close()
393
394
395# Точка входа в программу
396if __name__ == '__main__':
397 print('ЗапуÑкаю бота...')
398 print('{amount:0.8f}'.format(amount=float(call_api(command='returnBalances').get(CURRENCY_1, 0))))
399 print('{amount:0.8f}'.format(amount=float(call_api(command='returnBalances').get(CURRENCY_2, 0))))
400
401 try:
402 # Узнаем Ñвой Ð±Ð°Ð»Ð°Ð½Ñ Ñ‡ÐµÑ€ÐµÐ· Trading API
403 balances = call_api(command='returnBalances')
404 balance = float(balances.get(CURRENCY_1, 0))
405
406 if balance > 0:
407 decision = input('''
408 У Ð²Ð°Ñ Ð½Ð° баланÑе еÑть {amount:0.8f} {curr}
409 Ð’Ñ‹ дейÑтвительно хотите, что бы бот играл на вÑÑŽ валюту?
410 Введите Д/Y или Ð/N
411 '''.format(
412 amount=balance,
413 curr=CURRENCY_1))
414
415 if decision in ('N', 'n', 'Ð', 'н'):
416 print('Тогда избавьтеÑÑŒ от {curr} и перезапуÑтите бота'.format(curr=CURRENCY_1))
417 sys.exit(0)
418 except Exception as e:
419 print(str(e))
420
421 # ОÑновной цикл
422 while True:
423 main_flow()
424 time.sleep(1)