Как торговать через API на Yobit.net

На горизонте неизвестный континент - биржа Yobit.net. Больше 1000 активных (порой весьма экзотических) пар. Отличное место что бы что-то автоматизировать. Хотя бы даже без денег, так, поизучать. Погнали)

Public API

Как и большинство конкурентов, биржа предоставляет для роботов публичный и приватный методы получения данных. Прочитать детальное описание можно тут: https://www.yobit.net/ru/api/.

ХИНТ: у тех, кто работает с ноута без мыши, может не работать скролл текста. На самом деле там есть маленький серый скролл справа сверху, придется его двигать.

Попробуем для начала вытащить текущие пары и их активные курсы покупок и продаж?

Обычно именно для этого и используется метод ticker, но увы, тут он не предусмотрен выводить информацию по всем парам, можно лишь указывать нужные - например https://yobit.net/api/3/ticker/ltc_btc-nmc_btc.

Жаль, но давайте посмотрим, какую информацию можно получить. Для начала перейдем по этой ссылке сами, в браузере, что видим? Полезную информацию:

Отобразим в более удобном виде для чтения (jsoneditoronline.org):

Почитаем, что где что означает..

  • high: макcимальная цена
  • low: минимальная цена
  • avg: средняя цена
  • vol: объем торгов
  • vol_cur: объем торгов в валюте
  • last: цена последней сделки
  • buy: цена покупки
  • sell: цена продажи
  • updated: последнее обновление кэша

Судя по всему, поля buy и sell – это лучшие текущие цены покупки и продажи соответственно. Попробуем их вывести сначала для отдельно взятой пары. Пусть это будет ltc_btc.

 (Тем, кто не установил себе Python, нужно это сделать, что бы повторять написанное далее – как установить питон и модули к нему, я писал в этой статье).

Запускаем Idle, и пишем туда текст скрипта по получению данных. Сохраняем (под любым именем, у меня он называется yobit0.py) и запускаем (f5).

Скрипт:

import json
import requests

res = requests.get('https://yobit.net/api/3/ticker/ltc_btc') # получаем данные ticker'а
res_obj = json.loads(res.text) # переводим полученный текст в объект с данными

print("SELL: %0.8f" % res_obj['ltc_btc']['sell'])
print("BUY: %0.8f" % res_obj['ltc_btc']['buy'])

Результат:

Теперь нам нужно научиться получать название пары, buy и sell для всех пар – но всего на бирже больше 1000 пар – задолбаешься проверять каждую, даже прописывая по несколько штук… хотя… 

Активные пары можно посмотреть в методе info - https://yobit.net/api/3/info. Давайте ради интереса вытащим все активные пары и попробуем подставить их в URL? Раньше, я помню, было ограничение на длину 1024 символа при отправке данных методом GET, но потом вроде бы ограничение ушло из браузеров и переместилось на сторону конфигурации веб-серверов, так что заодно и проверим что получилось.

Получение списка активных пар

Скрипт:

import json
import requests

res = requests.get('https://yobit.net/api/3/info') # получаем данные
res_obj = json.loads(res.text) # переводим полученный текст в объект с данными

print("Получено %d пар(ы)!" % len(res_obj['pairs']))

pairs = '-'.join(res_obj['pairs']) # Формируем строку в нужном формате
print(pairs)

Результат:

Все пары получены, и сформированы в строку, разделенную дефисами – за это отвечает функция join – но не заостряйте пока на этом внимание. Попробуем теперь эту строку отдать на вход ticker`у и посмотрим, что получится. Юхуу.

Меняем этот скрипт (или создаем новый файл, yobit2.py), запускаем и смотрим ответ сервера…

Скрипт:

import json
import requests

res = requests.get('https://yobit.net/api/3/info') # получаем данные
res_obj = json.loads(res.text) # переводим полученный текст в объект с данными

print("Получено %d пар(ы)!" % len(res_obj['pairs']))

pairs = '-'.join(res_obj['pairs']) # Формируем строку в нужном формате
print(pairs)

О, горе, горе… Yobit использует веб-фронтенд Nginx версии 1.11.4, которому очень не нравится ссылка, которую мы сформировали.

  Ладно, ладно…. Плохой Yobit, тогда будем разбивать массив полученных данных на кусочки по 50 пар, и получать инфу, потом следующие 50 пар.. Можно было бы взять по 100 пар, но тогда  Yobit их отрезает и считает что ничего не передали) Можно просто идти в цикле по каждой паре, но это будет 1000 запросов к сайту, на месте Yobit’а я бы насторожился. Итак, режем список по 50 пар и выводим значения:

Скрипт:

import json
import math
import requests

res = requests.get('https://yobit.net/api/3/info') # получаем данные info
res_obj = json.loads(res.text) # переводим полученный текст в объект с данными

pairs = [pair for pair in res_obj['pairs']] # создадим массив названий пар
cnt = 1
# Проходим в цикле, отбирая каждый раз по 100 пар (или меньше, в хвосте)
for i in range(0, int(math.ceil(len(pairs)/50))):
    pairs_str = '-'.join(pairs[i*50:(i+1)*50]) # формируем строку для передачи тикеру

    ticker_res = requests.get('https://yobit.net/api/3/ticker/'+pairs_str) # получаем данные info
    ticker_res_obj = json.loads(ticker_res.text) # переводим полученный текст в объект с данными

    for pair in ticker_res_obj:
        print(
            cnt, 
            pair,
            '%0.8f' % ticker_res_obj[pair]['buy'],
            '%0.8f' % ticker_res_obj[pair]['sell']
        )
        cnt += 1

Результат:

Bingo, Yobit побежден, все 1002 пары получены.. Двигаемся дальше.



Private API

Методы, которыми мы пользовались выше, относились к публичному API – т.е. данные доступны для всех без регистрации. Сейчас посмотрим приватное API – методы, доступные только для авторизованного пользователя, позволяющие выполнять действия от его имени.

Давайте попробуем получить свой баланс и создать ордер используя только программирование. Для этого сначала нужно получить ключи API (а для этого нужно быть зарегистрированным).

Нажать Create new key

Обратите внимание - слева от кнопки есть выпадающий список (но менять его не надо).

Info only – Вы сможете запрашивать баланс, открытые ордера, историю торгов и т.п., но не сможете торговать, отменять ордера и выполнять прочие активные действия.

Info & trade & deposits (по умолчанию, оставляем как есть) – помимо информации вы сможете торговать а так же пополнять баланс. Если хакер украдет ваши ключи API, он не сможет вывести у вас деньги (но сможет их проиграть).

Info & trade & deposits & withdrawals – вы сможете выполнять  все доступные на сегодня методы API, включая вывод денег. Я бы не стал такое включать, пока в этом нет действительной нужды (но если пишете бота для межбиржевого арбитража, наверное стоит включить)

После этого получите ключи и сохраните их, они нам понадобятся.

Ну а теперь пора наваять простецкую функцию для доступа по private API и погонять её. Возьмем за основу код из статьи про Эксмобота, немного адаптируем, ну и запустим.

Впишите API-ключи в API_KEY и API_SECRET!

Скрипт:

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

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

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

# Будем перехватывать все сообщения об ошибках с биржи
class YobitException(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 YobitException(obj['error'])
        return obj
    except json.decoder.JSONDecodeError:
        raise YobitException('Ошибка анализа возвращаемых данных, получена строка', response)


print ('Получаем информацию по аккаунту', '*'*30)
print( call_api(method="getInfo") )

try:
    print ('Создаем ордер на покупку', '*'*30)
    print( call_api(method="Trade", pair="ltc_btc", type="buy", rate="0.1", amount=0.01) )
except YobitException as e:
    print("Облом:", e)

try:
    print ('Создаем ордер на продажу', '*'*30)
    print( call_api(method="Trade", pair="ltc_btc", type="sell", rate="0.1", amount=0.01) )
except YobitException as e:
    print("Облом:", e)

try:
    print ('Получаем список активных ордеров', '*'*30)
    print( call_api(method="ActiveOrders", pair="ltc_btc") )
except YobitException as e:
    print("Облом:", e)

try:
    print ('Получаем информацию по ордеру', '*'*30)
    print( call_api(method="OrderInfo", order_id="123") )
except YobitException as e:
    print("Облом:", e)

try:
    print ('Отменяем ордер', '*'*30)
    print( call_api(method="CancelOrder", order_id="123") )
except YobitException as e:
    print("Облом:", e)

try:
    print ('Получаем историю торгов', '*'*30)
    print( call_api(method="TradeHistory", pair="ltc_btc") )
except YobitException as e:
    print("Облом:", e)

try:
    print ('Получаем кошель для пополнения (BTC)', '*'*30)
    print( call_api(method="GetDepositAddress", coinName="BTC") )
except YobitException as e:
    print("Облом:", e)    
    

Результат:

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



Заключение

Теперь, когда вы умеете получать информацию по активным парам, курсам, можете создавать , отменять и проверять ордера, вам остается только выстроить логику работы бота – когда что покупать, когда что продавать, и т.п. За основу можете взять алгоритм работы эксмобота из соседней статьи этого цикла, и поменять методы и поля в них – и все у вас получится.

Желаю вам большого, стабильного и легкого заработка!



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


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

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

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

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



Комментарии
05.10.2017 14:45:51
Получение всех пар с ценами выдает:
 Traceback (most recent call last):
  File "yobit.py", line 11, in <module>
    for i in range(0, math.ceil(len(pairs)/50)):
TypeError: range() integer end argument expected, got float.


                                                </module>
ПроголосоватьПроголосовать
0 0
05.10.2017 16:29:43
Тогда для надежности заменим на 
for i in range(0, int(math.ceil(len(pairs)/50))):

(обновил код в примере)
ПроголосоватьПроголосовать
0 0
05.10.2017 15:43:00
добрый день вопрос такого плана ,в данном примере делается запрос на историю только на одну валюту, как сделать так что б вся история читалась
  И еще вопрос как сделать что б все открытые ордера показывались
ПроголосоватьПроголосовать
0 0
05.10.2017 16:38:55
Боюсь, и в том и в другом случае нужно указывать нужные пары, т.е. сначала запускать для ltc_btc, потом тоже самое для ltc_zec и т.п., другой возможности yobit не дает, почему-то (
Причем, для истории торгов не утверждается (хотя подразумевается) что вернутся только ваши торги, так что лучше проверять)

И ого, у них уже под 6 000 пар)
ПроголосоватьПроголосовать
0 0
05.10.2017 15:49:55
Валар М. выборка всех пар работает нормально ошибок нет ,смотрите где то у вас косяк в коде ,только что проверил питон 3.6 и пайчарм
ПроголосоватьПроголосовать
0 0
06.10.2017 08:48:06
Ошибка походу из-за питона 2.7
ПроголосоватьПроголосовать
0 0
06.10.2017 18:05:00
Еще вопросик, как вывести переменную со значением  pair за прeделы цикла for?
ПроголосоватьПроголосовать
0 0
06.10.2017 19:43:26
представляю что можно через сохранение в файл, а более не нубские способы существуют?
ПроголосоватьПроголосовать
0 0
06.10.2017 20:54:43
Можно вообще цикл не делать

# Например, можно вывести все содержимое
ticker_res_obj = json.loads(ticker_res.text)
print(ticker_res_obj) 

# Или из содержимого нужное значение
if 'ltc_btc' in ticker_res_obj:
        print(ticker_res_obj['ltc_btc'])

ну и тп
ПроголосоватьПроголосовать
0 0
08.10.2017 19:04:13
Подскажите причину (в Вашем коде только вставил коды API, и закомментировал вызов функций, связанных с выставлением и снятием ордеров)

Получаем информацию по аккаунту ******************************
Traceback (most recent call last):
  File "D:\Python workspace\exercise.py", line 67, in call_api
    raise YobitException(obj['error'])
YobitException: invalid nonce (has already been used)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "D:\Python workspace\exercise.py", line 74, in <module>
    print( call_api(method="getInfo") )
  File "D:\Python workspace\exercise.py", line 69, in call_api
    except json.decoder.JSONDecodeError:
AttributeError: 'module' object has no attribute 'JSONDecodeError'

Уже не в первой программе возникают ошибки Traceback (most recent call last), при этом видимых причин вроде бы нет, ведь насколько я понял такие ошибки возникают при несоответствии типов данных. Может ли у этого быть какая-то нетривиальная причина? Просто ошибка эта выскакивает регулярно даже на примерах из интернета где всё должно работать. Я что-то делаю не так, или мне нужно настроить/скачать библиотеки? 

                                                </module>
ПроголосоватьПроголосовать
0 0
08.10.2017 20:02:28
в заграничном инете https://stackoverflow.com/questions/44714046/python3-unable-to-import-jsondecodeerror-from-json-decoder говорят, что это из-за версии Python 3.4.
ПроголосоватьПроголосовать
0 0
09.10.2017 01:19:13
В общем на  windowsXP который у меня стоял поддержка Python только до версии 3.4. Установил параллельно Linux mint, и там апгрейдил Python и Idle до версии 3.6.2. Одна из ошибок больше не отображается, но другие остались. 

Получаем информацию по аккаунту ******************************
Traceback (most recent call last):
  File "/home/serg/Python workspace/exercise.py", line 63, in <module>
    print( call_api(method="getInfo") )
  File "/home/serg/Python workspace/exercise.py", line 56, in call_api
    raise YobitException(obj['error'])
YobitException: invalid nonce (has already been used)

Подскажите почему опять возникает ошибка Traceback (most recent call last), и взаимосвязано ли это с invalid nonce. В файлике nonce счётчик работает. Так как пишет, что это значение уже использовалось, то я заменил в этом файлике число на десяток больше текущего, но ничего не изменилось - всё равно пишет, что это значение уже использовалось. Это тоже как-то связано с ошибками интерпретатора? Или что-то с ключами API? 
                                                </module>
ПроголосоватьПроголосовать
0 0
09.10.2017 11:17:48
Такс, по порядку. Да, JSONDecodeError не существовало в одной из версий, но можно заменить на ValueError. Впрочем, обновление версии питона более правильная идея.
Насчет nonce - тут заморочка с криптографией, к коду бота не относится. Возможно, вы пробовали какого-то другого бота и там счет пошел уже на миллионы, или указали API ключи в каком-то приложении для смартфона, и он там шпарит увеличивает.. 

Самое простое решение в данном случае - пересоздать ключи API на бирже - тогда можно снова начинать с единицы.

Если интересно узнать более подробно про nonce - как и почему - читайте в комментариях к статье https://bablofil.ru/bot-dlya-birjy-exmo/, там этот вопрос поднимался.
ПроголосоватьПроголосовать
0 0
09.10.2017 21:45:37
О, заработало! Спасибо, добрый человек! Добавил нулей в файлик nonce - думал бестолку будет, потому что я ключи API на бирже новые создал как раз перед началом работы с этим кодом, ну то есть nonce эти у меня нулёвые были. Но как только заработало, так сразу и дошло в чём причина ошибки. Я, чтобы устранить проблему не восприятия версией Python 3.4, установил Linux, Python 3.6.2 и создал новый файл с этим же кодом и уже теми же ключами API. Но было необходимо перенести в ту же папку с новым файлом и файлик nonce с текущим значением, а я этого не сделал и программа создала новый файл и заново начала отсчёт. И главное мне пишет же почти по-русски: этот nonce уже использовался, а у меня в голове сидит мысль, что такого быть не может, как раз из-за того, что я типа предупредил развитие подобной ситуации, создав новые ключи API. В общем, спасибо ещё раз)
ПроголосоватьПроголосовать
0 0
09.10.2017 13:02:51
код бы увидеть чтоб не гадать может вы просто тупо где то лишнее закомментировали
ПроголосоватьПроголосовать
0 0
09.10.2017 21:49:46
Да не, саму функцию call_api я не трогал, закомментировал только некоторые вызовы этой функции, чтобы программа не насоздавала мне не нужных ордеров)
ПроголосоватьПроголосовать
0 0
10.10.2017 11:52:31
не работает конструкция, не выбирает валюту, подскажите что не так

balances = call_api(method="getInfo", funds=['eth'])
ПроголосоватьПроголосовать
0 0
10.10.2017 19:22:19
getInfo не принимает никаких параметров, а возвращает всё, что есть. Нужно как то так:

balances = call_api(method="getInfo")
eth = balances['return']['funds']['eth']
print(eth)
ПроголосоватьПроголосовать
0 0
12.10.2017 08:36:25
Спасибо с этим разобрался. Питон пока знаю плохо. Изучаю по мере переписывания вашего скрипта для yobit.net
Еще вопрос.
Пишу
deals = call_api(method="TradeHistory", pair=CURRENT_PAIR)
print (deals)
ответ {'success': 1}

если пишу 
print (deals['return']['amount']['rate'])

в результате
Traceback (most recent call last):
  File "D:\tmp\bot\yobitbot.py", line 88, in <module>
    print (deals['success']['return']['amount'])
TypeError: 'int' object is not subscriptable

Подскажите что не правильно?
                                                </module>
ПроголосоватьПроголосовать
0 0
17.10.2017 13:48:03
Тут два момента - во первых, в return возвращается набор сделок, т.е. надо персонально к каждой обращаться (мне не нравится, и я не понимаю зачем yobit так сделали).
Во вторых, amount и rate суть разные вещи, а в вашем коде вы непонятно что запрашиваете.
Вот так будет работать:

deals = call_api(method="TradeHistory", pair=CURRENT_PAIR)

for deal in deals['return']:
    print('Сделка #%s' % deal)
    print('Amount:', deals['return'][deal]['amount'])
    print('Rate:', deals['return'][deal]['rate'])
ПроголосоватьПроголосовать
0 0
17.10.2017 15:38:03
Спасибо!
Работает. Я понял, что надо считывать в цикле. Но так как не знаю языка, не смог правильно написать код.
ПроголосоватьПроголосовать
0 0
17.10.2017 23:06:25
Доброго времени суток. 
Подскажите, пожалуйста, хочу передавать первую валюту пары X / BTC, а также количество BTC, которое хочу потратить, из вводимых переменных, но не могу разобраться где это указать.

try:
    print ('Создаем ордер на покупку', '*'*30)
    print( call_api(method="Trade", pair="%s_btc", type="buy", rate="0.1", amount=0.01)%(ValutaX) )
except YobitException as e:
    print("Облом:", e)

Выше объявлю их ввод...amount - это то количество, которое хочу купить, правильно? А если я не знаю сколько будет, например LTC на мои 0.00999999 BTC?
ПроголосоватьПроголосовать
0 0
17.10.2017 23:11:17
Или стоит amount выразить через соотношение "количество BTC, которое хочу потратить" к " res_obj['ltc_btc']['buy']"?..
ПроголосоватьПроголосовать
0 0
17.10.2017 23:55:21
Это как раз на ваше усмотрение.

Вы знаете, что у вас есть 0.009BTC.
Вы можете:
А. Купить нужное вам количество LTC - например 10шт. В таком случае amount = 10, rate (высчитываете сами)=0.0009
Б. Купить по текущему (или желаемому курсу) сколько получится. Если желаемый курс 0.009, то rate=0.009, а amount (считаете сами) = 10
Это все, конечно, упрощенно, т.к. еще же надо учесть комиссию, которую возьмет биржа Т.е. купите вы не 10 LTC а 9.998 грубо говоря. 
А вообще примеры расчетов можно взять в этой статье, тут комиссии учитываются как при покупке, так и при продаже
https://bablofil.ru/bot-dlya-birjy-exmo

И еше замечание - вот так будет правильнее
 print( call_api(method="Trade", pair="%s_btc"  % ValutaX, type="buy", rate="0.1", amount=0.01) )
ПроголосоватьПроголосовать
0 0
19.10.2017 13:40:54
Добрый день пишу вам по поводу роботов для бирж на эксмо запустил работает первый Профит 4$ перевел на ваш кошелёк большое спасибо на полониксе Сейчас пробую столкнулся с рядом проблем но так как в программировании полный ноль обращаюсь к вам если по возникновению ошибок буду напрямую вас беспокоить по эмайлу или Фэйсбуке  поможете ? Вчера скачал самоучитель по питону попробую уложить в голову. Заранее благодарен
ПроголосоватьПроголосовать
0 0
19.10.2017 22:51:09
Спасибо) Конечно, без проблем, пишите
ПроголосоватьПроголосовать
0 0
19.10.2017 13:51:16
Писал на ваш mail  пишет неверный Адрес...
ПроголосоватьПроголосовать
0 0
19.10.2017 22:51:41
Получил ваше письмо, отвечу там)
ПроголосоватьПроголосовать
0 0
Пожалуйста, авторизуйтесь, что бы оставить свой комментарий