Выкладываю реализацию бота для wex.nz.
Суть его - если ничего не куплено по паре, то выставляет ордер на покупку, отслеживает, если ордер не сработал, отменяет через указанное время, если сработал - выставляет ордер на продажу с профитом, ждет исполнения до победного. Если продажа сработала, начинает все сначала.
Важно: на балансе не должно быть первой валюты! Т.е. если играете на пару ltc_usd, то избавьтесь от всех ltc - купите на них доллары или там мороженное, не знаю, главное, что бы на балансе был ноль! Тогда все будет хорошо, и никаких неприятных сюрпризов не случится.
Для определения цены покупки берет записи из стакана (параметр OFFERS_AMOUNT), и берет из них среднюю. Если указать OFFERS_AMOUNT = 1 то будет брать по текущей лучшей цене.
О запуске - для тех, кто недавно присоединился:
1. Нужно скачать питон версии 3.6+ с официального сайта
2. После установки запустить командную строку (cmd) вбить туда pip install requests + Enter
3. Код, выложенный на сайте, скопировать в блокнот и сохранить (например wex_bot.py)
3. Получить на Wex ключи API и прописать в файл бота (поставьте галочки trade & info на бирже, при создании ключа).
4. В настройках указать на какую пару хотите играть и на какую сумму
5. Запустить бота (в командной строке введите python путь_к_файлу_wex_bot.py)
Настройка
В коде бота нужно прописать API ключи, вот тут, буква b'' во второй строчке нужна, просто вставьте апи-ключи в кавычки
# Вписываем свои ключи
API_KEY = 'TW0......NB'
API_SECRET = b'191....02'
Пропишите пару, на которую собираетесь играть, в данном случае у меня пара eur_usd, поэтому на балансе изначально должно быть 0 евро
# Тонкая настройка
CURRENCY_1 = 'eur'
CURRENCY_2 = 'usd'
Остальное вроде бы прописано,
CAN_SPEND - это сколько CURRENCY_2 тратить (долларов в данном случае)
ORDER_LIFE_TIME - через сколько минут отменять buy, если он не сработал
PROFIT_MARKUP - какой навар желаем с каждого раунда купли/продажи
ORDER_LIFE_TIME = 3 # через сколько минут отменять неисполненный ордер на покупку CURRENCY_1
STOCK_FEE = 0.002 # Комиссия, которую берет биржа (0.002 = 0.2%)
OFFERS_AMOUNT = 1 # Сколько предложений из стакана берем для расчета средней цены
CAN_SPEND = 5 # Сколько тратить CURRENCY_2 каждый раз при покупке CURRENCY_1 (5$ в моем случае)
PROFIT_MARKUP = 0.001 # Какой навар нужен с каждой сделки? (0.001 = 0.1%)
DEBUG = True # True - выводить отладочную информацию, False - писать как можно меньше
Непосредственно код:
import os
import json
import requests
import urllib, http.client
import hmac, hashlib
import time
# Вписываем свои ключи
API_KEY = 'TW.....NB'
API_SECRET = b'191....e02'
# Тонкая настройка
CURRENCY_1 = 'eur'
CURRENCY_2 = 'usd'
ORDER_LIFE_TIME = 3 # через сколько минут отменять неисполненный ордер на покупку CURRENCY_1
STOCK_FEE = 0.002 # Комиссия, которую берет биржа (0.002 = 0.2%)
OFFERS_AMOUNT = 1 # Сколько предложений из стакана берем для расчета средней цены
CAN_SPEND = 5 # Сколько тратить CURRENCY_2 каждый раз при покупке CURRENCY_1 (5$ в моем случае)
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("wex.nz", 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']:
print(response.decode('utf-8'))
raise ScriptError(obj['error'])
return obj
except ValueError:
raise ScriptError('Ошибка анализа возвращаемых данных, получена строка', response)
# Узнаем лимиты по парам
pair_settings = json.loads(requests.get("https://wex.nz/api/3/info").text)['pairs']
# Реализация алгоритма
def main_flow():
try:
# Получаем список активных ордеров
opened_orders = []
try:
wex_orders = call_api(method="ActiveOrders", pair=CURR_PAIR)['return']
for order in wex_orders:
o = wex_orders[order]
o['order_id']=order
opened_orders.append(o)
except ScriptError:
pass
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, которую можно продать?
"""
Высчитываем курс для продажи.
Нам надо продать всю валюту, которую купили, на сумму, за которую купили + немного навара и минус комиссия биржи
При этом важный момент, что валюты у нас меньше, чем купили - бирже ушла комиссия
0.00134345 1.5045
Поэтому курс продажи может получиться довольно высоким
"""
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="{r:0.8f}".format(r=round(wanna_get/float(balances[CURRENCY_1]),pair_settings[CURR_PAIR]['decimal_places'])),
amount="{a:0.8f}".format(a=round(balances[CURRENCY_1],8))
)['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://wex.nz/api/3/depth/"+CURR_PAIR+"?limit="+str(OFFERS_AMOUNT)).text)[CURR_PAIR]
prices = [bid[0] for bid in offers['bids']]
try:
avg_price = sum(prices)/len(prices)
"""
Посчитать, сколько валюты CURRENCY_1 можно купить на сумму CAN_SPEND
"""
# Купить как есть, потом продать с учетом комиссии
my_need_price = avg_price
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="{r:0.8f}".format(r=round(my_need_price,8)), amount="{a:0.8f}".format(a=round(my_amount,8)))['return']
print(new_order)
if DEBUG:
print('Создан ордер на покупку', new_order['order_id'])
except ZeroDivisionError:
print('Не удается вычислить среднюю цену', prices)
else:
raise ScriptQuitCondition('Выход, не хватает денег')
except ScriptError as e:
print('ScriptError', e)
except ScriptQuitCondition as e:
print('ScriptQuitCondition', e)
except Exception as e:
print("!!!!",e)
while(True):
main_flow()
time.sleep(1)
Заключение
Бот проверен, покупку/продажу/отмену создает, в минус не уходит (хотя может купить на пике и соответственно притормозиться и ждать роста). Если будут замечены какие-то косяки в работе, пишите, поправим.
Вот как в процессе тестирования я заработал один цент с 10 долларов (выставлял 0.1% прибыльности):
Кому интересно, как устроено API у Wex и какие там есть методы, можете пройтись по этим ссылкам - официальная документация (https://wex.nz/api/3/docs#info, https://wex.nz/tapi/docs#Trade), а так же посмотреть примеры в статье про Yobit - у них одинаковое API.
Так что всем удачи и хороших заработков!
Мне кажется стоит поступить как тут с эксмо - https://bablofil.ru/macd-python-stock-bot/ - брать сделки и строить свечи самому
2 предложения по Вашему сайту:
- делить комменты по страницам. С моим провайдером нижний коммент в некоторых темах подгружается за 2 минуты, не меньше.
- добавлять новые функции в Ваших ботов и продавать их по адекватным ценам. Я бы купил хорошо продуманного бота, реагирующего на свечи и прочие биржебеды, которые не может предусмотреть текущий бот.
Каждый раз, когда биржа принимает от вас запрос по API, она проверяет - новый ли это запрос, или какой-то заблудившийся, или кто-то перехватывает ваши пакеты и от вашего имени отправляет.
Поэтому каждый раз nonce должно быть выше предыдущего, и предыдущий nonce был запомнен биржей.
Откуда он взялся - другой вопрос. Возможно, вы ставили какое-то мобильное приложение или тестировали другого бота, который этот счетчик передвинул.
Сейчас биржа вам сообщает - в прошлый раз приходил nonce 185872, а вы сейчас присылаете 17 - такой запрос отклонен.
У вас есть несколько вариантов
- сменить ключи API (nonce у каждого комплекта своё, отсчет начнется с нуля)
- либо в файле nonce прописать руками скажем 190000 - число, большее указанного
- удалить файл nonce и в коде заменить out.write('1') на out.write('190000')
и т.п.
При этом имейте в виду, что если эти же API ключи используются еще где-то (в мобильном приложении, например), то они будут друг другу мешать, т.к. каждое из них будет увеличивать счетчик независимо.
Wex это вроде как позволяет.
Скажите, а есть возможность боту вписать логику ставить ордер на продажу по самому выгодному курсу из стакана но не меньше чем указанная мною маржинальность, буду очень признательна!)
Но бот продает и так с МИНИМАЛЬНО выгодной ценой, дешевле продавать невыгодно, какие бы цены ни были в стакане
Может внести в скрип небольшие изменения на данный вариант. Типа минимальная ставка продаваемой валюты..?
Купил......
Продал....
Отменил....
Но разумеется только на терминальных стадиях чтоб не перегружать информацией.
А то я больше программист чем трейдер....
С точки зрения бота правильно, т.к. нужно получить профит.
А так разные трейдеры по разному поступают - кто-то продает в минус, кто-то продает купленые альты за другие альты, кто-то докупает еще что бы сбить цену (https://bablofil.ru/weighted-average/), кто-то торгует другими парами что бы продать эту и компенсировать убытки, кто-то ждет - фундаментальный анализ никто не отменял, какие-то монеты обязательно вырастут, какие-то вряд ли, и т.п..
Код открыт, каждый может изменить под себя, именно так, как ему надо
1. При указании любой суммы в строке CAN_SPEND = пишет "Выход, не хватает денег" (хотя они там есть)
2. Можно ли использовать не какую-то конкретную сумму, а весь баланс целиком? Если да, то как можно это сделать?
Буду признателен за помощь
1. Возможно, сумма для торгов слишком маленькая
2. Тут надо переделывать алгоритм, т.к. боту нужно знать на какую сумму продавать купленное, а для этого нужно запоминать, сколько было потрачено
Т.е. грубо говоря бот купил 0.001 монет, а к тому моменту, как собрался продавать, на балансе 0.0002 монеты. А у него заложено, если купил на 10 долларов, то и продать нужно на 10 долларов + навар. Вот ему и приходится продавать монеты втридорога что бы вернуть своё
Спасибо бот заработал сразу без проблем. Просто хотел сказать что для запуска не обязательно использовать командную строку, можно просто повторно открыть страницу питона открыть бота и F5 и в общем то они без проблем работают. Большое спасибо за проделанную работу и не только за ботов, а и за другие статьи очень интересно и познавательно.
но от советов не откажусь )))
А так же есть описание каждого метода https://bablofil.ru/bot-dlya-birgi-yobit/
Там, правда, про Yobit, но ёбит своё апи полностью скопировал с btc-e (он же wex), так что можете брать код оттуда за основу
Обновил код в статье, попробуйте еще раз
Может быть связано с тем, что биржа получала цены не в формате 0.1010101 а в формате 1e-8