Продолжаем развлекаться - соорудим бота для биржи Bittrex.com.
Регистрацию я расписывать не буду, она довольно тривиальна, а вот с подключением через API «есть нюанс» (с). Дело в том, что биржа параноидальна серьезно относится к безопасности пользователей и сохранности их средств, так что приготовьтесь к многочисленным подтверждениям того, что вы это вы. Так вот.
Подключаем API на Bittrex.com
В меню выбираем Settings – API Keys, и с грустью тоской смотрим на надпись:
To use API Keys, you must have Two-Factor Authentication enabled.
Для использования API необходимо подключить двуфакторную авторизацию. Круто, ну давайте включим. Там же, в Settings, переходим в раздел Two-Factor Authentication и с еще большей болью в сердце видим, что Bittrex использует только и исключительно Google Authenticator.
Пару слов о Google Authenticator: Первый раз я столкнулся с Google Authenticator, когда подключал двуфакторную авторизацию на btc-e.nz. Тогда я подумал, ух ты, как здорово, ставишь приложение на телефон и авторизуешься когда тебе надо, можно симки менять.. Прошло несколько лет, мой телефон давно сломался, я давно купил новый, и вот мне потребовалось зайти на эту биржу и авторизоваться под старым акканутом.. И разверзся ад! Сначала Google меня допрашивал, я ли это, требовал всевозможных подтверждений, гонял по кругу, не верил мне.. Потом вроде заработало. Я попытался авторизоваться на бирже, но теперь биржа начала ругаться на то, что код неправильный. Я сдался, удалил приложение, и пошел делать то, что хотел, на другую биржу. Через несколько месяцев владельца btc-e арестовали, а биржу на месяц прикрыли, потом вроде как перекупили, так что вся эта канитель по сути спасла меня от потери денег. Но Google Authenticator мне все равно с тех пор не нравится.
Так вот. Для двуфакторной авторизации нам нужно установить на телефон Google Authenticator. Это официальное приложение, его можно найти в Play Market и iTunes. После установки приложение попросит вас добавить аккаунт.
Отсканируйте в приложении QR-код с сайта, введите цифры с приложения на сайт, потом перейдите на почту и подтвердите, что вы действительно решили подключить авторизацию. После этого можно работать с ключами API.
Возвращаемся в Settings – API Keys, запускаем Google Authenticator, указываем на странице цифры из приложения, и указываем права доступа – я не стал включать вывод средств, ибо вроде бы незачем. Но вот описание:
- Read Info – Можно получать информацию о балансе и других свойствах аккаунта
- Withdraw – Вывод средств на указанный вами адрес. Эта опция может быть полезной для проведения арбитража или переводе средств в cold storage.
- Trade Limit – Позволяет выставлять отложенные ордера по лимиту
- Trade Market – Позволяет выставлять отложенные ордера по рынку. ПРЕДУПРЕЖДЕНИЕ (так на сайте написано): Не включайте эту опцию, если точно не уверены, что делаете. Покупки по рынку чрезвычайно опасны!
В общем я включил первые две опции. Насчет предупреждения не понял, т.к. в API нет метода, что бы торговать по рынку. Ну да ладно.
После выставления настроек вводим новые цифры из Google Authenticator, нажимаем Update Keys и ура! Получилось.
Совет – сразу эти ключи сохраните куда-нибудь. Тут строго относятся к безопасности, если забудете, придется новые создавать.
Проверяем работу через API
Методы для работы API биттрекса расписаны тут. Изменим немного функцию из бота для эксмо, и попробуем что-то сделать.
Я закинул на баланс немного LTC и создал ордер на продажу по завышенной цене. Проверим, видно ли это через API запросы:
import time
import json
import urllib, http.client
import hmac, hashlib
API_KEY = '...'
# обратите внимание, что добавлена 'b' перед строкой
API_SECRET = b'...'
API_URL = 'bittrex.com'
API_VERSION = 'v1.1'
# Свой класс исключений
class ScriptError(Exception):
pass
# все обращения к API проходят через эту функцию
def call_api(**kwargs):
http_method = kwargs.get('http_method') if kwargs.get('http_method', '') else 'POST'
method = kwargs.get('method')
nonce = str(int(round(time.time())))
payload = {
'nonce': nonce
}
if kwargs:
payload.update(kwargs)
uri = "https://" + API_URL + "/api/" + API_VERSION + method + '?apikey=' + API_KEY + '&nonce=' + nonce
uri += urllib.parse.urlencode(payload)
payload = urllib.parse.urlencode(payload)
H = hmac.new(key=API_SECRET, digestmod=hashlib.sha512)
H.update(uri.encode())
sign = H.hexdigest()
apisign = hmac.new(API_SECRET,
uri.encode(),
hashlib.sha512).hexdigest()
headers = {"Content-type": "application/x-www-form-urlencoded",
"Key": API_KEY,
"apisign": apisign}
conn = http.client.HTTPSConnection(API_URL, timeout=60)
conn.request(http_method, uri, 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)
print(call_api(method='/account/getbalances'))
print(call_api(method='/market/getopenorders', market='USDT-LTC'))
На выходе получаем:
Баланс: {'success': True, 'message': '', 'result': [{'Currency': 'LTC', 'Balance': 0.48107406, 'Available': 0.47107406, 'Pending': 0.0, 'CryptoAddress': 'LMGJrSr37DYpZGiWEpLzTEz7my6PUu48KP'}]}
Открытые ордера: {'success': True, 'message': '', 'result': [{'Uuid': None, 'OrderUuid': '07dd349c-936f-426e-be6a-c3ad6d6d0bf4', 'Exchange': 'USDT-LTC', 'OrderType': 'LIMIT_SELL', 'Quantity': 0.01, 'QuantityRemaining': 0.01, 'Limit': 56.90798125, 'CommissionPaid': 0.0, 'Price': 0.0, 'PricePerUnit': None, 'Opened': '2017-11-05T08:47:53.18', 'Closed': None, 'CancelInitiated': False, 'ImmediateOrCancel': False, 'IsConditional': False, 'Condition': 'NONE', 'ConditionTarget': None}]}
Отлично, всё работает, можно начинать делать бота.
Получаем данные для индикаторов
В официальном API нет метода, который позволил бы получить значения для свечей. Тем не менее, существует неофициальный API v2.0, который используется самим биттрексом, и о котором они никому ничего не рассказывают. Я обнаружил это, изучая запросы между моим браузером и биржей в панели разработчика (F12 - Network), позже, погуглив, обнаружил что люди уже повытаскивали все запросы и даже их расписали. Эту информацию вы можете найти в гугле, мне же нужна одна функция – получение данных свечей.
Вот как выглядит запрос:
https://bittrex.com/Api/v2.0/pub/market/GetTicks?marketName=USDT-LTC&tickInterval=fiveMin
Если требуется интервал по полчаса, то fiveMin меняем на thirtyMin, и т.п.
У этих данных, впрочем, есть недостаток – они возвращают данные за прошедшие периоды.. Т.е. если строить по минутам, то отставание составит 3 минуты, если по 5 минут, то как минимум 5 минут и т.п. Для оперативного принятия решений этого мало, поэтому недостающий набор данных я планирую брать с публичного метода getmarkethistory:
https://bittrex.com/api/v1.1/public/getmarkethistory?market=USDT-LTC.
Т.е. последние несколько минут торгов я буду брать с истории торгов, и дополнять ими то, что недополучил вначале – так я соберу наиболее полную картину. Более того, как раз последние несколько секунд и минут могут как раз оказаться решающими. Напишем код, проверим:
import time
import json
import requests
from datetime import datetime
MARKET = 'USDT-BTC'
def get_ticks():
chart_data = {}
# Получаем готовые данные свечей
res = requests.get("https://bittrex.com/Api/v2.0/pub/market/GetTicks?marketName=" + MARKET + "&tickInterval=fiveMin")
for item in json.loads(res.text)['result']:
try:
dt_obj = datetime.strptime(item['T'], '%Y-%m-%dT%H:%M:%S.%f')
except ValueError:
dt_obj = datetime.strptime(item['T'], '%Y-%m-%dT%H:%M:%S')
ts = int(time.mktime(dt_obj.timetuple()))
if not ts in chart_data:
chart_data[ts] = {'open': float(item['O']), 'close': float(item['C']), 'high': float(item['H']), 'low': float(item['L'])}
# Добираем недостающее
res = requests.get("https://bittrex.com/api/v1.1/public/getmarkethistory?market=" + MARKET)
for trade in reversed(json.loads(res.text)['result']):
try:
dt_obj = datetime.strptime(trade['TimeStamp'], '%Y-%m-%dT%H:%M:%S.%f')
except ValueError:
dt_obj = datetime.strptime(item['T'], '%Y-%m-%dT%H:%M:%S')
ts = int((time.mktime(dt_obj.timetuple())/300))*300 # округляем до 5 минут
if not ts in chart_data:
chart_data[ts] = {'open': 0, 'close': 0, 'high': 0,'low': 0}
chart_data[ts]['close'] = float(trade['Price'])
if not chart_data[ts]['open']:
chart_data[ts]['open'] = float(trade['Price'])
if not chart_data[ts]['high'] or chart_data[ts]['high'] < float(trade['Price']):
chart_data[ts]['high'] = float(trade['Price'])
if not chart_data[ts]['low'] or chart_data[ts]['low'] > float(trade['Price']):
chart_data[ts]['low'] = float(trade['Price'])
return chart_data
# Выведем результат, посмотрим - работаем по времени биржи
chart_data = get_ticks()
for item in sorted(chart_data):
print(datetime.fromtimestamp(item),chart_data[item])
Результат:
2017-11-05 11:10:00 {'open': 54.75500015, 'close': 54.75500015, 'high': 54.75500015, 'low': 54.75500015}
2017-11-05 11:15:00 {'open': 54.87745003, 'close': 54.89744999, 'high': 54.89744999, 'low': 54.87745003}
2017-11-05 11:20:00 {'open': 54.87755003, 'close': 55.03567601, 'high': 55.03567601, 'low': 54.87755003}
2017-11-05 11:25:00 {'open': 55.03567601, 'close': 55.0, 'high': 55.03567601, 'low': 55.0}
2017-11-05 11:30:00 {'open': 55.00668, 'close': 55.0, 'high': 55.00668, 'low': 55.0}
2017-11-05 11:35:00 {'open': 55.0, 'close': 55.0, 'high': 55.0, 'low': 55.0}
2017-11-05 11:40:00 {'open': 55.0, 'close': 55.0, 'high': 55.0, 'low': 55.0}
2017-11-05 11:45:00 {'open': 55.0, 'close': 55.0, 'high': 55.0, 'low': 55.0}
2017-11-05 11:50:00 {'open': 54.8975, 'close': 54.79500025, 'high': 55.0, 'low': 54.79500025}
2017-11-05 11:55:00 {'open': 54.915338, 'close': 54.79500001, 'high': 54.915338, 'low': 54.79500001}
Для анализа этих данных мы будем использовать MACD, как и в предыдущей статье, потому что, во-первых, это сэкономит время и размер статьи, а во-вторых, потому что мы будем в этот раз использовать значения по другому.
Итак, принцип алгоритма расписан здесь, сделаем небольшую функцию, которая будет возвращать нам тренд и совет, пора или не пора торговать. Я подразумеваю, что тренд растет (growing), если это либо бык в активной фазе, либо медведь в затухающей. Код выглядит вот так:
def get_macd_advice(chart_data):
macd, macdsignal, macdhist = talib.MACD(numpy.asarray([chart_data[item]['close'] for item in sorted(chart_data)]),
fastperiod=12, slowperiod=26, signalperiod=9)
idx = numpy.argwhere(numpy.diff(numpy.sign(macd - macdsignal)) != 0).reshape(-1) + 0
trand = 'BULL' if macd[-1] > macdsignal[-1] else 'BEAR'
max_v = 0
activity_time = False
growing = False
for offset, elem in enumerate(macdhist):
growing = False
curr_v = macd[offset] - macdsignal[offset]
if abs(curr_v) > abs(max_v):
max_v = curr_v
perc = curr_v / max_v
if ((macd[offset] > macdsignal[offset] and perc * 100 > BULL_PERC) # восходящий тренд
or (
macd[offset] < macdsignal[offset] and perc * 100 < (100 - BEAR_PERC)
)
):
activity_time = True
growing = True
if offset in idx and not numpy.isnan(elem):
# тренд изменился
max_v = curr_v = 0 # обнуляем пик спреда между линиями
return ({'trand':trand, 'growing':growing})
Соответственно, при работе мы будем вызывать эту функцию, передавать ей полученные ранее данные и узнавать, пора ли покупать/продавать и т.п.
Стратегия
В этот раз предлагаю поступить так – бот будет закупать на падении графика, когда MACD все уже падает, но тренд уже начал разворот, и будет продавать купленное на росте тренда, пока он все еще растет, но уже начинает спад.
Таким образом, он будет стараться купить дешевле, но продать как можно дороже, а если курс вырастет недостаточно и пойдет на спад, то будет выставлять по минимально допустимой цене (с учетом комиссии при покупке, при продаже и требуемого навара).
И это, вроде бы и относительно безопасно, но, как выяснилось, работает оочень долго – сначала бот ждет покупки на спаде, потом ждет роста. С другой стороны, значительно уменьшается шанс залезть на самый пик и никогда ничего не продать.
Но, тем не менее, предусмотрена опция отключения такого поведения - USE_MACD = True заставит бота думать, USE_MACD = False включит ковбойский режим. Если режим MACD включен, управлять настройками задумчивости можно через переменные BEAR_PERC и BULL_PERC (подробнее рассмотрено тут) – выставляйте каждую от 0 и до 101 и смотрите, как лучше.
В общем-то вот код, скачивайте, пользуйтесь, может еще какие-то идеи придут. Удачи вам)
P.S. Если вы раньше не запускали ботов с моего сайта, то знайте, вам нужно установить Python с модулями (описано тут) и ta-lib (описано тут). О всяких других аспектах, таких как работа с API или комиссиями, читайте в статьях цикла, ссылки под этой статьёй.
Статистика по результатам тестирования
Через 6 суток непрерывной работы бота я снял статистику - выкачал историю сделок с биттрекса, и посчитал чистый доход за вычетом комиссий.
Играл на 8 пар, каждой выделял по 2 доллара. Из-за политики биржи, еще 0.25% прибавлялись к платежам, так что каждый ордер на покупку обходился примерно в 2,00499762 доллара.
Профитность ставил 0.1% с каждой сделки.
В итоге за 6 дней было совершено 144 сделки (72 цикла покупки/продажи), прибыль с каждой составила большей частью 0.1%, в некоторых случаях больше (иногда на пару порядков больше).
Итоговый доход с 16 долларов, заряженных в игру, составил 1,23 - или 7.6%.
Зависших пар (купленных на пике) - не обнаружено.
Продолжаем наблюдение..
Вообще пока не планировал ботов для других бирж, но подумаю. За FreeOTP спасибо)
С нетерпением буду ждать робота для WEX, если что )
А про лимиты на битрексе ничего не сказано, только общее - не более 500 открытых ордеров и не более 200 000 ордеров в сутки.
У меня неделю работал на 8 пар (в одном боте), не забанили
Можно внутри базы удалить записи по конкретным ордерам, но наверное вам не так нужна история, её можно и с биржи скачать.
Обновил код, скачайте и запустите новый
Плюс еще синтаксическая ошибка.
Я уж подумал, что есть какой-то хитрый план.
Спасибо за ответ.
Кстати, я изменил параметр на пятиминутку, слегка модифицировал код, чтобы бот закупался по одним параметрам MACD, а продавал по другим. Торговля побойчее пошла. Вроде...
Понимаю, что для Вас этот бот скорее хобби, и врятли Вы уделяете теме большое внимание, но все же.
На всякий случай задам вопросы здесь. Если повезет, через пару дней ответы получу ;)
1. Анализируя работу бота заметил, что бот покупает на падении. Сильном. Без какого-либо признака разворота. Т.е. линии сходятся, но разворота не намечается. На растущем рынке такие его действия не особо болезненны для депозита, рано или поздно курс вырастет, но все же. К тому же он покупает, когда обе скользящие пересекаются находясь ниже нулевой линии, что, в принципе, не логично, ведь курс в этот момент все еще может падать. Подозреваю, что виновата сравнение значений переменных по модулю в процедуре MACD. Или я не прав? Может я какой параметр перекрутил?
2. На сайте есть видео с описанием еще одного индикатора - стохастического RSI. Как я понял, с его помощью можно предотвратить закупку бота в момент перекупленности, когда скользящие сходятся и вроде можно закупиться, а через мгновение цена улетает вниз. Не ради праздного интереса спрашиваю. Удалось "прикрутить" второй индикатор к боту. Если да, его результативность увеличилась?
Так же, по аналогии с этим вариантом, пытался закрывать уже открытый ордер по достижении заданного возраста. Это условие так же не срабатывало.
Может в коде какая-то заморочка, не очевидная на первый взгляд?
Текущий курс продажи 187.99597746
Создаю ордер на продажу по курсу 191.67569321
То есть, ордер на продажу создается даже тогда, когда нет подходящих ордеров на покупку. Вполне естественно, что ордер зависает:)
Вообще, конечно, не скрипт виноват, что так происходит, а сам инструмент MACD - он сигнализирует, что пора продавать, а цена-то стала еще меньше, чем была при покупке. Эх, не наварить мне миллионы:)
Если вы про бота с macd, то прям уж большого смысла не имеет увеличение до месяца- тут важнее всего, что бы бот получил последние 2-3 пересечения линий macd, и понимал, в какую сторону движется. По факту нужно только последнее пересечение, но оно могло произойти как полчаса назад, так и неделю, поэтому берем с запасом. Но если сделаете месяц, чуть-чуть подстрахуетесь. Вообще зависит от динамики выбранной пары
Есть еще один вопрос, но скорее по самому биттрексу. Где у меня не хватает? На каком счету и какая сумма должна быть изначально?
Пишет USDT-BTC
Не удалось создать ордер: недостаточно средств
Надо подстраиваться, увеличивать..
Мне в свое время понравился биттрикс именно тем, что можно было ставить любую ставку, включая самые копеечные. Я перенес скрипты с полоникса на битрикс, и через неделю ставки изменились и я не смог играть так, как задумавал. Увы (
Когда-то давно писал скрипты для мета-трейдера, кажется, но вся эта идея с торговлей через брокера сводит любые скрипты на нет
Я слышал, некоторые VPN этим грешат )
Зачастую люди выставляют сразу заказов 5 с некоторым шагом от текущей цены, и при исполнении каждого (отменяют и) выставляют ордер на продажу
Думаю, вы можете и сами немного модифицировать код - создавать ордера на покупку в цикле, при исполнении продажи отменять все открытые ордера на покупку, при исполнении покупки отменять продажу и на всё создавать новую...
Такое может быть, если, например, рынок указан не правильно
В боте у меня стояла пара USDT-BCC, а такой, оказывается, уже нет..
Я внес пару дополнений в код, что бы было видно сообщение если что, скачайте по новой
BTC-XMR Создаем ордер на покупку
BTC-XMR Создаем ордер на покупку и на этом все, ордера не открываются, баланс подходит, в чем может быть причина?
Может быть суммы торгов запредельные?
Например, CAN_SPEND = 2 будет означать 2 BTC...
Может быть что-то с апи ключами.. И в логе еще что-то должно быть, мне кажется