Бот для Wex.nz

Выкладываю реализацию бота для 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=round(wanna_get/float(balances[CURRENCY_1]),pair_settings[CURR_PAIR]['decimal_places']),
                    amount=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=round(my_need_price,8), amount=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. 

Так что всем удачи и хороших заработков!


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

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

Не забудьте рассказать друзьям об этой статье.
Чтобы поддержать ресурс Bablofil достаточно просто поделиться с друзьями этой статьей в социальных сетях. Каждый репост - это самая высокая оценка качества материала. Спасибо, что читаете этот блог.



Комментарии
22.01.2018 18:07:34
Добрый день. Большое спасибо за статью. Есть вопрос к вам, не знаете случайно как данные из WEX достать для графиков и индикаторов? В статье про Bittrex вы говорили, " Я обнаружил это, изучая запросы между моим браузером и биржей в панели разработчика (F12 - Network)", может быть для WEX тоже что-нибудь такое есть?
ПроголосоватьПроголосовать
0 0
22.01.2018 19:55:30
Простого способа не вижу..
Мне кажется стоит поступить как тут с эксмо - https://bablofil.ru/macd-python-stock-bot/ - брать сделки и строить свечи самому
ПроголосоватьПроголосовать
0 0
22.01.2018 20:26:06
Автору грандиозный респект и уважение! Переведу комплимент как только заработаю! :) Большое спасибо!

2 предложения по Вашему сайту:
- делить комменты по страницам. С моим провайдером нижний коммент в некоторых темах подгружается за 2 минуты, не меньше.
- добавлять новые функции в Ваших ботов и продавать их по адекватным ценам. Я бы купил хорошо продуманного бота, реагирующего на свечи и прочие биржебеды, которые не может предусмотреть текущий бот.
ПроголосоватьПроголосовать
0 0
31.01.2018 08:05:29
а можете подсказать, что делать в ситуации, когда, выдает такую хрень, при запуске:

{"success":0,"error":"invalid nonce parameter; on key:185871, you sent:'17', you should send:185872"}
{"success":0,"error":"invalid nonce parameter; on key:185871, you sent:'18', you should send:185872"}
ScriptError invalid nonce parameter; on key:185871, you sent:'18', you should send:185872
{"success":0,"error":"invalid nonce parameter; on key:185871, you sent:'19', you should send:185872"}

удаление файла nonce и изменение в нем параметра на нужную цифру не дало результата (((
ПроголосоватьПроголосовать
0 0
31.01.2018 08:46:20
Смотрите - каждый раз, когда вы отправляете запрос через API, вы передаете некую переменную nonce, которая каждый раз увеличивается.
Каждый раз, когда биржа принимает от вас запрос по API, она проверяет - новый ли это запрос, или какой-то заблудившийся, или кто-то перехватывает ваши пакеты и от вашего имени отправляет.
Поэтому каждый раз nonce должно быть выше предыдущего, и предыдущий nonce был запомнен биржей.
Откуда он взялся - другой вопрос. Возможно, вы ставили какое-то мобильное приложение или тестировали другого бота, который этот счетчик передвинул.
Сейчас биржа вам сообщает - в прошлый раз приходил nonce 185872, а вы сейчас присылаете 17 - такой запрос отклонен.
У вас есть несколько вариантов
- сменить ключи API (nonce у каждого комплекта своё, отсчет начнется с нуля)
- либо в файле nonce прописать руками скажем 190000 - число, большее указанного
- удалить файл nonce и в коде заменить out.write('1') на out.write('190000')
и т.п.
При этом имейте в виду, что если эти же API ключи используются еще где-то (в мобильном приложении, например), то они будут друг другу мешать, т.к. каждое из них будет увеличивать счетчик независимо.
ПроголосоватьПроголосовать
1 0
31.01.2018 08:48:22
Спасибо, за быстрый и развернутый ответ. Т.е. одновременно запустить 2 и более бота на вексе будет проблематично из-за этого счетчика запросов?
ПроголосоватьПроголосовать
0 0
31.01.2018 10:48:36
Вы можете создать отдельные ключи для каждого бота, и всё будет хорошо)
Wex это вроде как позволяет.
ПроголосоватьПроголосовать
1 0
17.02.2018 16:22:34
Доброго времени! Андрей, если я правильно понимаю, то бот по сути аналогичен ситуации если просто сидеть в крипте, если произошел рост на указанный процент он продает крипту и тут же откупает и опять ждет роста. по сути смысла торговать им нет, или я что то не учел?
ПроголосоватьПроголосовать
0 0
20.02.2018 11:45:56
Огромное спасибо разработчику, только недавно начала пользоваться и уже скоро с прибыли смогу поблагодарить автора)

Скажите, а есть возможность боту вписать логику ставить ордер на продажу по самому выгодному курсу из стакана но не меньше чем указанная мною маржинальность, буду очень признательна!)
ПроголосоватьПроголосовать
0 0
Пожалуйста, авторизуйтесь, что бы оставить свой комментарий
Крипто-кошельки для помощи и благодарности проекту:

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

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

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