Автоматическая ребалансировка портфеля с API AsterDEX
Этот продвинутый туториал покажет, как создать Python-скрипт для автоматической ребалансировки вашего криптовалютного портфеля на бирже AsterDEX.
Что такое ребалансировка и зачем ее автоматизировать?
Ребалансировка портфеля — это процесс восстановления исходного процентного соотношения активов. Например, вы решили, что ваш портфель должен состоять из 50% BTC и 50% ETH. Из-за колебаний рынка через месяц соотношение может стать 60% BTC и 40% ETH. Ребалансировка вернет его к 50/50 путем продажи избытка BTC и покупки недостающего ETH.
Автоматизация этого процесса с помощью API помогает поддерживать дисциплину, экономит время и устраняет эмоциональный фактор из принятия решений.
Необходимые условия:
- Знания из нашего руководства по созданию бота на Python, особенно функция отправки подписанных запросов.
- Установленный Python и библиотека `requests`.
Шаг 1: Определение целевого портфеля и настройка
В первую очередь, определим нашу идеальную структуру портфеля. Мы также соберем в одном месте все необходимые функции из предыдущего туториала, включая функцию для отправки подписанных запросов (подробнее о ней читайте в гайде по HMAC-аутентификации).
import requests
import hmac
import hashlib
import time
# --- НАСТРОЙКИ ---
API_KEY = "YOUR_API_KEY"
SECRET_KEY = "YOUR_SECRET_KEY"
BASE_URL = "https://fapi.asterdex.com"
# Наша целевая структура портфеля (в сумме должно быть 1.0)
TARGET_ALLOCATION = {
'BTC': 0.5, # 50%
'ETH': 0.3, # 30%
'USDT': 0.2 # 20% (стейблкоин для стабильности)
}
# --- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ (из прошлого туториала) ---
def send_signed_request(method, path, params=None):
if params is None:
params = {}
timestamp = int(time.time() * 1000)
params['timestamp'] = timestamp
query_string = '&'.join([f"{key}={params[key]}" for key in sorted(params)])
signature = hmac.new(SECRET_KEY.encode('utf-8'), query_string.encode('utf-8'), hashlib.sha256).hexdigest()
params['signature'] = signature
url = BASE_URL + path
headers = {'X-MBX-APIKEY': API_KEY}
try:
if method.upper() == 'POST':
response = requests.post(url, headers=headers, params=params)
elif method.upper() == 'GET':
response = requests.get(url, headers=headers, params=params)
else:
return None
return response.json()
except Exception as e:
print(f"Ошибка при отправке запроса: {e}")
return None
def place_order(symbol, side, quantity, price=None, order_type="MARKET"):
path = "/fapi/v1/order"
params = {
"symbol": symbol,
"side": side.upper(),
"type": order_type.upper(),
"quantity": f"{quantity:.8f}".rstrip('0').rstrip('.') # Форматирование количества
}
if order_type.upper() == "LIMIT":
params["timeInForce"] = "GTC"
params["price"] = price
print(f"Размещение ордера: {side} {quantity} {symbol} по цене {price if price else 'рыночной'}")
# Для теста используйте '/fapi/v1/order/test'
# response = send_signed_request('POST', path + '/test', params)
response = send_signed_request('POST', path, params)
print("Ответ биржи:", response)
return response
Шаг 2: Получение текущего баланса и цен
Создадим функции для получения баланса активов и их текущих рыночных цен. Цены нам нужны, чтобы оценить общую стоимость портфеля.
def get_account_balance():
"""Получает баланс и возвращает словарь вида {'BTC': 1.5, 'USDT': 3000}"""
path = "/fapi/v2/balance"
balances_raw = send_signed_request('GET', path)
if not balances_raw:
return {}
balances = {item['asset']: float(item['balance']) for item in balances_raw}
return balances
def get_market_prices():
"""Получает цены и возвращает словарь вида {'BTCUSDT': 60000.0}"""
path = "/fapi/v1/ticker/price"
prices_raw = send_signed_request('GET', path)
if not prices_raw:
return {}
prices = {item['symbol']: float(item['price']) for item in prices_raw}
return prices
Шаг 3: Расчет текущего и целевого распределения
Это ядро нашего скрипта. Здесь мы вычисляем общую стоимость портфеля в USDT и сравниваем текущее распределение с целевым.
def calculate_allocations():
balances = get_account_balance()
prices = get_market_prices()
if not balances or not prices:
print("Не удалось получить данные о балансе или ценах.")
return None
# Оставляем только те активы, которые есть в нашей целевой структуре
portfolio = {asset: amount for asset, amount in balances.items() if asset in TARGET_ALLOCATION}
# Расчет общей стоимости портфеля в USDT
total_portfolio_value_usdt = 0
for asset, amount in portfolio.items():
if asset == 'USDT':
total_portfolio_value_usdt += amount
else:
# Находим цену актива по отношению к USDT
symbol = f"{asset}USDT"
if symbol in prices:
total_portfolio_value_usdt += amount * prices[symbol]
if total_portfolio_value_usdt == 0:
print("Общая стоимость портфеля равна нулю.")
return None
print(f"
Общая стоимость портфеля: ${total_portfolio_value_usdt:.2f}")
# Расчет текущего и целевого распределения в USDT
current_alloc_values = {}
target_alloc_values = {}
for asset, target_pct in TARGET_ALLOCATION.items():
# Целевая стоимость
target_value = total_portfolio_value_usdt * target_pct
target_alloc_values[asset] = target_value
# Текущая стоимость
current_value = 0
if asset == 'USDT':
current_value = portfolio.get(asset, 0)
else:
symbol = f"{asset}USDT"
if symbol in prices:
current_value = portfolio.get(asset, 0) * prices[symbol]
current_alloc_values[asset] = current_value
print(f"Актив {asset}:")
print(f" Текущая стоимость: ${current_value:.2f} ({(current_value / total_portfolio_value_usdt) * 100:.2f}%)")
print(f" Целевая стоимость: ${target_value:.2f} ({target_pct * 100:.2f}%)")
return {
"current": current_alloc_values,
"target": target_alloc_values,
"prices": prices
}
Шаг 4: Генерация и выполнение ордеров для ребалансировки
Теперь, зная разницу между текущим и целевым состоянием, мы можем сгенерировать ордера. Логика проста: продаем избыточные активы, а на вырученные USDT покупаем недостающие.
ВНИМАНИЕ: Этот скрипт будет исполнять реальные сделки. Начните с очень маленьких сумм или используйте тестовый эндпоинт, как показано в функции `place_order`.
def rebalance_portfolio():
allocations = calculate_allocations()
if not allocations:
return
current_values = allocations['current']
target_values = allocations['target']
prices = allocations['prices']
# --- Шаг 1: Продаем избыточные активы (кроме USDT) ---
print("
--- Продажа избыточных активов ---")
for asset, current_value in current_values.items():
if asset == 'USDT':
continue
target_value = target_values[asset]
if current_value > target_value:
# Считаем, сколько нужно продать
value_to_sell = current_value - target_value
symbol = f"{asset}USDT"
if symbol in prices:
amount_to_sell = value_to_sell / prices[symbol]
# Здесь нужно добавить проверку на минимальный размер ордера
print(f"Планируем продать {amount_to_sell:.6f} {asset}")
place_order(symbol, "SELL", amount_to_sell)
# --- Шаг 2: Покупаем недостающие активы (кроме USDT) ---
# (Нужно подождать, пока ордера на продажу исполнятся и баланс USDT обновится)
print("
Ожидание исполнения ордеров на продажу (в реальном боте здесь нужна проверка статуса)...")
time.sleep(10) # Простое ожидание
# Обновляем баланс USDT
updated_balances = get_account_balance()
usdt_balance = updated_balances.get('USDT', 0)
print(f"Обновленный баланс USDT: {usdt_balance}")
print("
--- Покупка недостающих активов ---")
for asset, current_value in current_values.items():
if asset == 'USDT':
continue
target_value = target_values[asset]
if current_value < target_value:
value_to_buy = target_value - current_value
if usdt_balance >= value_to_buy:
symbol = f"{asset}USDT"
if symbol in prices:
amount_to_buy = value_to_buy / prices[symbol]
print(f"Планируем купить {amount_to_buy:.6f} {asset}")
place_order(symbol, "BUY", amount_to_buy)
usdt_balance -= value_to_buy # Уменьшаем доступный баланс
else:
print(f"Недостаточно USDT для покупки {asset}")
# Запуск ребалансировки
rebalance_portfolio()
Заключение и важные доработки
Этот скрипт является мощной основой для автоматической ребалансировки. Однако для реального использования его необходимо серьезно доработать.
Ключевые улучшения:
- Проверка минимального размера ордера: Каждая торговая пара имеет фильтр `MIN_NOTIONAL` (минимальная стоимость ордера). Перед размещением ордера нужно убедиться, что `количество * цена > MIN_NOTIONAL`.
- Обработка комиссий: Торговые комиссии уменьшают итоговый баланс. Их нужно учитывать в расчетах.
- Умное ожидание ордеров: Вместо `time.sleep()` нужно циклически проверять статус отправленных ордеров через API, чтобы приступать к покупке только после их полного исполнения.
- Логирование: Подробно записывайте все действия скрипта в файл для последующего анализа и отладки.