Кстати говоря да, у меня же есть код бота для liqui.io..
Работает абсолютно идентично боту для эксмо.. Тот же алгоритм и тот же, фактически код.
На этом статья окончена :) Вставляйте API ключи и играйте.
Вот код:
import urllib, http.client
import os
import time
import json
# эти модули нужны для генерации подписи API
import hmac, hashlib
# ключи API, которые предоставила liqui
API_KEY = ''
# обратите внимание, что добавлена 'b' перед строкой
API_SECRET = b''
# Тонкая настройка
CURRENCY_1 = 'ltc'
CURRENCY_2 = 'btc'
CURRENCY_1_MIN_QUANTITY = 0.0001 # минимальная сумма ставки
ORDER_LIFE_TIME = 3 # через сколько минут отменять неисполненный ордер на покупку CURRENCY_1
STOCK_FEE = 0.002 # Комиссия, которую берет биржа (0.002 = 0.2%)
AVG_PRICE_PERIOD = 90 # За какой период брать среднюю цену
CAN_SPEND = 0.0002 # Сколько тратить CURRENCY_2 каждый раз при покупке CURRENCY_1
PROFIT_MARKUP = 0.001 # Какой навар нужен с каждой сделки? (0.001 = 0.1%)
DEBUG = True# True - выводить отладочную информацию, False - писать как можно меньше
STOCK_TIME_OFFSET = 0 # Если расходится время биржи с текущим
NONCE = 1
CURR_DIR = os.path.dirname(os.path.abspath(__file__))
# базовые настройки
API_URL = 'api.liqui.io'
API_VERSION = 'api/3'
# Свой класс исключений
class ScriptError(Exception):
pass
class ScriptQuitCondition(Exception):
pass
CURRENT_PAIR = (CURRENCY_1 + '_' + CURRENCY_2).lower()
# все обращения к API проходят через эту функцию
def call_api(api_method, http_method="POST", public=True, **kwargs):
payload = {}
if not public:
try:
nonce_file = open(CURR_DIR + "/nonce", "r+")
except FileNotFoundError:
nonce_file = open(CURR_DIR + "/nonce", "w+")
try:
nonce = int(nonce_file.read())
except ValueError:
nonce = 0
nonce = (NONCE if not nonce else nonce)+1
nonce_file.close()
nonce_file = open(CURR_DIR + "/nonce", "w")
nonce_file.write(str(nonce))
nonce_file.close()
payload = {'nonce': nonce}
if kwargs:
payload.update(kwargs)
payload = urllib.parse.urlencode(payload)
#print("payload", 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(API_URL)
conn.request(http_method, ("" if public else '/tapi') + ("/"+API_VERSION if public else '') + ("/"+ api_method if public else ''), payload, headers)
response = conn.getresponse().read()
#print("response", response)
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:
# Получаем список активных ордеров
try:
opened_orders_dict = call_api(api_method="", http_method="POST", public=False, method="ActiveOrders", pair=CURRENT_PAIR )['return']###
opened_orders = []
for order in opened_orders_dict:
curr_order = opened_orders_dict[order]
curr_order['order_id'] = order
opened_orders.append(curr_order)
except (KeyError, ScriptError):
if DEBUG:
print('Открытых ордеров нет')
opened_orders = []
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_history = call_api(api_method="", http_method="POST", public=False, method="OrderInfo", order_id=order['order_id'])['return'][order['order_id']]
if order_history['status'] == 0 and order_history['start_amount'] != order_history['amount']:
# по ордеру уже есть частичное выполнение, выход
raise ScriptQuitCondition('По ордеру уже есть частичное выполнение, выход, продолжаем надеяться докупить валюту по тому курсу, по которому уже купили часть')
else:
if DEBUG:
print('Частично исполненных ордеров нет')
time_passed = time.time() + STOCK_TIME_OFFSET*60*60 - int(order['timestamp_created'])
if time_passed > ORDER_LIFE_TIME * 60:
# Ордер уже давно висит, никому не нужен, отменяем
call_api(api_method="", http_method="POST", public=False, method="CancelOrder", order_id=order['order_id'])
raise ScriptQuitCondition('Отменяем ордер -за ' + str(ORDER_LIFE_TIME) + ' минут не удалось купить '+ str(CURRENCY_1))
else:
raise ScriptQuitCondition('Выход, продолжаем надеяться купить валюту по указанному ранее курсу')
else: # Открытых ордеров нет
balances = call_api(api_method="", http_method="POST", public=False, method="getInfo")['return']['funds']
if float(balances[CURRENCY_1]) >= CURRENCY_1_MIN_QUANTITY: # Есть ли в наличии CURRENCY_1, которую можно продать?
"""
Высчитываем курс для продажи.
Нам надо продать всю валюту, которую купили, на сумму, за которую купили + немного навара и минус комиссия биржи
При этом важный момент, что валюты у нас меньше, чем купили - бирже ушла комиссия
0.00134345 1.5045
"""
wanna_get = "%.8f" % (CAN_SPEND + CAN_SPEND * (STOCK_FEE+PROFIT_MARKUP)) # сколько хотим получить за наше кол-во
rate = "%.8f" % (float(wanna_get)/float(balances[CURRENCY_1]))
print('sell', balances[CURRENCY_1], wanna_get, rate)
new_order = call_api(api_method="", http_method="POST", public=False, method="Trade", pair=CURRENT_PAIR, type="sell", rate=rate, amount="%.8f" % (float(balances[CURRENCY_1]) - 0.0000001))['return']
print(new_order)
if DEBUG:
print('Создан ордер на продажу', CURRENCY_1, new_order['order_id'])
else:
# CURRENCY_1 нет, надо докупить
# Достаточно ли денег на балансе в валюте CURRENCY_2 (Баланс >= CAN_SPEND)
if float(balances[CURRENCY_2]) >= CAN_SPEND:
# Узнать среднюю цену за AVG_PRICE_PERIOD, по которой продают CURRENCY_1
deals = call_api(api_method="trades/"+CURRENT_PAIR)
prices = []
for deal in deals[CURRENT_PAIR]:
time_passed = time.time() + STOCK_TIME_OFFSET*60*60 - int(deal['timestamp'])
if time_passed < AVG_PRICE_PERIOD*60:
prices.append(float(deal['price']))
try:
avg_price = sum(prices)/len(prices)
"""
Посчитать, сколько валюты CURRENCY_1 можно купить.
На сумму CAN_SPEND за минусом STOCK_FEE, и с учетом PROFIT_MARKUP
( = ниже средней цены рынка, с учетом комиссии и желаемого профита)
"""
# купить больше, потому что биржа потом заберет кусок
my_need_price = avg_price + avg_price * (STOCK_FEE+PROFIT_MARKUP)
my_amount = CAN_SPEND/my_need_price
print('buy', my_amount, my_need_price)
# Допускается ли покупка такого кол-ва валюты (т.е. не нарушается минимальная сумма сделки)
if my_amount >= CURRENCY_1_MIN_QUANTITY:
new_order = call_api(api_method="", http_method="POST", public=False, method="Trade", pair=CURRENT_PAIR, type="buy", rate=my_need_price, amount=round(my_amount,8))['return']
print(new_order)
if DEBUG:
print('Создан ордер на покупку', new_order['order_id'])
else: # мы можем купить слишком мало на нашу сумму
raise ScriptQuitCondition('Выход, не хватает денег на создание ордера')
except ZeroDivisionError:
print('Не удается вычислить среднюю цену', prices)
else:
raise ScriptQuitCondition('Выход, не хватает денег')
except ScriptError as e:
print(e)
except ScriptQuitCondition as e:
if DEBUG:
print(e)
pass
while(True):
main_flow()
print('*'*80)
time.sleep(1)