Бот для Yobit

В общем всем привет, again.

Сегодня хочу выложить вам бота, собранного на основе бота для эксмо и материала из статьи про API для Yobita.

Это простой бот, картинку я выбрал что-то черезчур крутую :)

Немного упростил бота, и чуть-чуть изменил логику выставления цены. Этот бот, когда входит в торги, берет предложения из стакана, первые несколько штук (параметр OFFERS_AMOUNT) и выставляет цену по средней цене этих предложений.

Т.е. если указать OFFERS_AMOUNT = 3, то бот возьмет 3 цены с верхушки стакана, из них среднюю, и по ней выставит свою. Если указать OFFERS_AMOUNT=1, то выставит по текущей лучшей. Ну и т.п. Такая вот бешеная аналитика :)

Еще убрал параметры минимальной цены для торгов - бот будет стараться продать все, что купил, и купить на сколько сказали, не работает, значит не работает. Оно в принципе так и было, только с параметром.

В общем бот выставляет ордер на покупку, отслеживает, отменяет через указанное количество минут ORDER_LIFE_TIME если он все еще не выполнен. Если исполнен, то выставляет ордер на продажу. Тут небольшая хитрость с математикой, раз уж я её убрал при покупке (для возможности покупки по лучшей цене), то при продаже она возвращается и цена увеличивается сильнее чем для эксмо. Если там бот старался купить дешевле, что бы потом продать тоже дешевле, то тут бот покупает как есть, а при продаже учитывает обе комиссии + желаемый навар.

Не скажу, что бы я его очень сильно и долго гонял, но проверил, что ордера создаются, отменяются и продаются с наваром.. И решил с вами поделиться. Если что-то там не так, пишите, придумаем куда чего прикрутить.

В общем создавайте новые API ключи, вписывайте, запускайте, хорошего вам навара :)

Вот код:

import os
import json
import requests
import urllib, http.client
import hmac, hashlib
import time

# Вписываем свои ключи
API_KEY = '' 
API_SECRET = b''

# Тонкая настройка
CURRENCY_1 = 'btc' 
CURRENCY_2 = 'rur'

ORDER_LIFE_TIME = 3 # через сколько минут отменять неисполненный ордер на покупку CURRENCY_1
STOCK_FEE = 0.002 # Комиссия, которую берет биржа (0.002 = 0.2%)
OFFERS_AMOUNT = 1 # Сколько предложений из стакана берем для расчета средней цены
CAN_SPEND = 100 # Сколько тратить CURRENCY_2 каждый раз при покупке CURRENCY_1 (100р в моем случае)
PROFIT_MARKUP = 0.001 # Какой навар нужен с каждой сделки? (0.001 = 0.1%)
DEBUG = True # True - выводить отладочную информацию, False - писать как можно меньше

CURR_PAIR = CURRENCY_1.lower() + "_" + CURRENCY_2.lower()
"""
    Каждый новый запрос к серверу должен содержать увеличенное число в диапазоне 1-2147483646
    Поэтому храним число в файле поблизости, каждый раз обновляя его
"""
nonce_file = "./nonce"
if not os.path.exists(nonce_file):
    with open(nonce_file, "w") as out:
        out.write('1')

# Будем перехватывать все сообщения об ошибках с биржи
class ScriptError(Exception):
    pass
class ScriptQuitCondition(Exception):
    pass
        
def call_api(**kwargs):

    # При каждом обращении к торговому API увеличиваем счетчик nonce на единицу
    with open(nonce_file, 'r+') as inp:
        nonce = int(inp.read())
        inp.seek(0)
        inp.write(str(nonce+1))
        inp.truncate()

    payload = {'nonce': nonce}

    if kwargs:
        payload.update(kwargs)
    payload =  urllib.parse.urlencode(payload)

    H = hmac.new(key=API_SECRET, digestmod=hashlib.sha512)
    H.update(payload.encode('utf-8'))
    sign = H.hexdigest()
    
    headers = {"Content-type": "application/x-www-form-urlencoded",
           "Key":API_KEY,
           "Sign":sign}
    conn = http.client.HTTPSConnection("yobit.net", timeout=60)
    conn.request("POST", "/tapi/", payload, headers)
    response = conn.getresponse().read()
    
    conn.close()

    try:
        obj = json.loads(response.decode('utf-8'))

        if 'error' in obj and obj['error']:
            raise ScriptError(obj['error'])
        return obj
    except json.decoder.JSONDecodeError:
        raise ScriptError('Ошибка анализа возвращаемых данных, получена строка', response)

# Реализация алгоритма
def main_flow():
    
    try:
        # Получаем список активных ордеров
        opened_orders = []
        try:
            yobit_orders = call_api(method="ActiveOrders", pair=CURR_PAIR)['return']
            
            for order in yobit_orders:
                o = yobit_orders[order]
                o['order_id']=order
                opened_orders.append(o)
                
        except KeyError:
            if DEBUG:
                print('Открытых ордеров нет')

    
        sell_orders = []
        # Есть ли неисполненные ордера на продажу CURRENCY_1?
        for order in opened_orders:
            if order['type'] == 'sell':
                # Есть неисполненные ордера на продажу CURRENCY_1, выход
                raise ScriptQuitCondition('Выход, ждем пока не исполнятся/закроются все ордера на продажу (один ордер может быть разбит биржей на несколько и исполняться частями)')
            else:
                # Запоминаем ордера на покупку CURRENCY_1
                sell_orders.append(order)
                
        # Проверяем, есть ли открытые ордера на покупку CURRENCY_1
        if sell_orders: # открытые ордера есть
            for order in sell_orders:
                # Проверяем, есть ли частично исполненные
                if DEBUG:
                    print('Проверяем, что происходит с отложенным ордером', order['order_id'])
                # Получаем состояние ордера, если он еще не исполнен, отменяем
                order_status = call_api(method="OrderInfo", order_id=order['order_id'])['return'][str(order['order_id'])]['status']

                time_passed = time.time()  - int(order['timestamp_created'])

                if time_passed > ORDER_LIFE_TIME * 60:
                    # Ордер уже давно висит, никому не нужен, отменяем
                    call_api(method="CancelOrder", order_id=order['order_id']) 
                    raise ScriptQuitCondition('Отменяем ордер: за ' + str(ORDER_LIFE_TIME) + ' минут не удалось купить '+ str(CURRENCY_1))
                else:
                    raise ScriptQuitCondition('Выход, продолжаем надеяться купить валюту по указанному ранее курсу, со времени создания ордера прошло %s секунд' % str(time_passed))
                   
        else: # Открытых ордеров нет
            balances = call_api(method="getInfo")['return']['funds']
            if float(balances.get(CURRENCY_1, 0)) > 0: # Есть ли в наличии CURRENCY_1, которую можно продать?
                """
                    Высчитываем курс для продажи.
                    Нам надо продать всю валюту, которую купили, на сумму, за которую купили + немного навара и минус комиссия биржи
                    При этом важный момент, что валюты у нас меньше, чем купили - бирже ушла комиссия
                    Поэтому курс продажи может получиться доволно высоким
                """
                wanna_get = (CAN_SPEND + CAN_SPEND * PROFIT_MARKUP)/(1-STOCK_FEE)  # сколько хотим получить за наше кол-во
                print('sell', balances[CURRENCY_1], wanna_get, (wanna_get/float(balances[CURRENCY_1])))
                new_order = call_api(method="Trade", pair=CURR_PAIR, type="sell", rate=wanna_get/float(balances[CURRENCY_1]), amount=balances[CURRENCY_1])['return']
               
                print(new_order)
                if DEBUG:
                    print('Создан ордер на продажу', CURRENCY_1, new_order['order_id'])
            else:
                # CURRENCY_1 нет, надо докупить
                # Достаточно ли денег на балансе в валюте CURRENCY_2 (Баланс >= CAN_SPEND)
                if float(balances.get(CURRENCY_2, 0)) >= CAN_SPEND:
                    # Получаем информацию по предложениям из стакана
                    offers = json.loads(requests.get("https://yobit.net/api/3/depth/"+CURR_PAIR+"?limit="+str(OFFERS_AMOUNT)).text)[CURR_PAIR]
                    print(offers)
                    prices = [bid[0] for bid in offers['bids']]                    
                    print(prices)
                    try:        
                        avg_price = sum(prices)/len(prices)
                       
                        # Купить как есть, потом продать с учетом комиссии
                        my_need_price = avg_price# - avg_price * (STOCK_FEE+PROFIT_MARKUP) 
                        my_amount = CAN_SPEND/my_need_price
                        
                        print('buy: кол-во {amount:0.8f}, курс: {rate:0.8f}'.format(amount=my_amount, rate=my_need_price))
                        new_order = call_api(method="Trade", pair=CURR_PAIR, type="buy", rate=my_need_price, amount=my_amount)['return']
                       
                        print(new_order)
                        if DEBUG:
                            print('Создан ордер на покупку', new_order['order_id'])
                            
                        
                    except ZeroDivisionError:
                        print('Не удается вычислить среднюю цену', prices)
                else:
                    raise ScriptQuitCondition('Выход, не хватает денег')
        
    except ScriptError as e:
        print(e)
    except ScriptQuitCondition as e:
        print(e)
    #except Exception as e:
    #    print("!!!!",e)

main_flow()

while(True):
    main_flow()
    time.sleep(1)

 

Для тех, кто недавно присоединился:

1. Нужно скачать питон версии 3.6+ с официального сайта 

2. После установки запустить командную строку (cmd) вбить туда pip install requests + Enter

3. Код, выложенный на сайте, скопировать в блокнот и сохранить (например yobit_bot.py)

3. Получить на Yobite ключи API и прописать в файл бота.

4. В настройках указать на какую пару хотите играть и на какую сумму

5. Запустить бота (в командной строке введите python путь_к_файлу_yobit_bot.py)

Намного более подробной информации в ссылках в начале статьи и в других статьях цикла (см. ниже, над комментариями и НАД КНОПКАМИ ДОНАТА ;))

 


Это статья из цикла "Популярно о бирже"
Все статьи цикла:

Последнее изменение:


Крипто-кошельки для помощи и благодарности проекту:

Bitcoin адрес проекта: [[address]]

Перевод на сумму [[value]] BTC получен. Спасибо!.
[[error]]

Ethereum адрес проекта: [[address]]



Комментарии
14.01.2018 08:08:16
Бот работает! Ордера выставляет, снимает, все как надо.

в процессе выполнения пишет: ('Ошибка анализа возвращаемых данных, получена строка', b'')
ПроголосоватьПроголосовать
1 0
15.01.2018 08:01:24
Добрый день. Бот не несомненно рабочий. Но есть пара моментов:
1) в процессе выполнения пишет: ('Ошибка анализа возвращаемых данных, получена строка', b'') - я так понимаю эта ошибка сообщения с API_SECRET
2) по прошествии некоторого времени, ориентировочно 15 мин вылетает вот это (как говорится хороший стук  наружу выйдет) Traceback (most recent call last):
  File "C:\Users\1234\Desktop\боты\yobit max.py", line 178, in <module>
    main_flow()
  File "C:\Users\1234\Desktop\боты\yobit max.py", line 83, in main_flow
    yobit_orders = call_api(method="ActiveOrders", pair=CURR_PAIR)['return']
  File "C:\Users\1234\Desktop\боты\yobit max.py", line 63, in call_api
    response = conn.getresponse().read()
  File "C:\Users\1234\AppData\Local\Programs\Python\Python36-32\lib\http\client.py", line 1331, in getresponse
    response.begin()
  File "C:\Users\1234\AppData\Local\Programs\Python\Python36-32\lib\http\client.py", line 297, in begin
    version, status, reason = self._read_status()
  File "C:\Users\1234\AppData\Local\Programs\Python\Python36-32\lib\http\client.py", line 258, in _read_status
    line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
  File "C:\Users\1234\AppData\Local\Programs\Python\Python36-32\lib\socket.py", line 586, in readinto
    return self._sock.recv_into(b)
  File "C:\Users\1234\AppData\Local\Programs\Python\Python36-32\lib\ssl.py", line 1009, in recv_into
    return self.read(nbytes, buffer)
  File "C:\Users\1234\AppData\Local\Programs\Python\Python36-32\lib\ssl.py", line 871, in read
    return self._sslobj.read(len, buffer)
  File "C:\Users\1234\AppData\Local\Programs\Python\Python36-32\lib\ssl.py", line 631, in read
    v = self._sslobj.read(len, buffer)
socket.timeout: The read operation timed out</module>
ПроголосоватьПроголосовать
0 0
15.01.2018 09:14:22
Да, тоже вижу

('Ошибка анализа возвращаемых данных, получена строка', b'') - это означает, что Yobit вместо нормального ответа вернул пустую страницу
socket.timeout: The read operation timed out - значит что бот не смог дождаться ответа от сервера.

Скорее всего, это защита Yobit от ддоса, либо какая-то балансировка нагрузки либо отказ серверов на стороне биржи. Может быть, конечно, еще сетевые лаги, но вряд ли у разных людей из разных регионов были бы одинаковые проблемы. Так что предлагаю просто игнорировать эти ошибки, вреда от них вроде как нет..
ПроголосоватьПроголосовать
0 0
16.01.2018 06:49:04
Красава, спасибо, еще не тестил, вот такого бота искал. Допилить его можно, чтобы он анализировал на какой паре лучше купить, а на какой продать. ххх/rur, xxx/usd, xxx/eth, xxx/btc
ПроголосоватьПроголосовать
0 0
16.01.2018 08:23:07
как можно дописать, чтобы покупать с левого стакана, не выставляя ордер по цене правого стакана.
ПроголосоватьПроголосовать
0 0
Пожалуйста, авторизуйтесь, что бы оставить свой комментарий
Крипто-кошельки для помощи и благодарности проекту:

Bitcoin адрес проекта: [[address]]

Перевод на сумму [[value]] BTC получен. Спасибо!.
[[error]]

Ethereum адрес проекта: [[address]]