Розробка бота для ребалансування портфеля
Ребалансуючий бот підтримує задану аллокацію активів у портфелі. Якщо BTC виросла з 50% до 65% через ріст ціни, бот продає частину BTC та докупує інші активи до цільових ваг. Це дисциплінований підхід до управління портфелем: автоматично фіксує прибуток у виросших активах та купує недооцінені.
Стратегії ребалансування
Calendar rebalancing (за часом)
Ребалансування за розкладом незалежно від відхилень:
# Тижнева ребалансування кожний понеділок о 12:00 UTC
schedule = {
'type': 'calendar',
'interval': 'weekly',
'day': 'monday',
'time': '12:00'
}
Просто та передбачуваного, але може совпасти з неблагоприятним рыночним моментом.
Threshold rebalancing (за відхиленням)
Ребалансування тільки коли вага активу відхилиться від ціль на N%:
def needs_rebalancing(
current_weights: dict,
target_weights: dict,
threshold_percent: float = 5.0
) -> bool:
for asset, target_weight in target_weights.items():
current = current_weights.get(asset, 0)
deviation = abs(current - target_weight)
if deviation >= threshold_percent:
return True
return False
Більш ефективно: ребалансуємо тільки коли дійсно потрібно.
Hybrid approach
Перевіряємо щодня, але ребалансуємо тільки при відхиленні >5%:
class HybridRebalancer:
def should_rebalance(self, portfolio: Portfolio) -> bool:
current_weights = portfolio.get_weights()
max_deviation = max(
abs(current_weights[a] - self.target_weights[a])
for a in self.target_weights
)
return max_deviation >= self.threshold
Реалізація бота
Розрахунок ребалансуючих ордерів
from decimal import Decimal
from typing import dict
class RebalancingCalculator:
def calculate_trades(
self,
current_balances: dict[str, Decimal],
current_prices: dict[str, Decimal],
target_weights: dict[str, float] # сума = 100
) -> list[RebalanceTrade]:
# Рахуємо поточну вартість портфеля
total_value = sum(
current_balances[asset] * current_prices[asset]
for asset in current_balances
)
trades = []
for asset, target_weight in target_weights.items():
target_value = total_value * Decimal(str(target_weight / 100))
current_value = current_balances.get(asset, Decimal(0)) * current_prices[asset]
diff_value = target_value - current_value
if abs(diff_value) < Decimal('10'): # Мінімум $10 для торговлі
continue
diff_quantity = diff_value / current_prices[asset]
trades.append(RebalanceTrade(
asset=asset,
side='buy' if diff_value > 0 else 'sell',
quantity=abs(diff_quantity),
value_usd=abs(diff_value),
current_weight=float(current_value / total_value * 100),
target_weight=target_weight
))
return trades
def optimize_trade_order(self, trades: list[RebalanceTrade]) -> list[RebalanceTrade]:
"""Спочатку продаємо, потім купуємо — не потрібен додатковий USDT"""
sells = [t for t in trades if t.side == 'sell']
buys = [t for t in trades if t.side == 'buy']
return sells + buys
Виконання ребалансування
class RebalancingBot:
async def execute_rebalance(self) -> RebalanceReport:
# Отримуємо поточний стан
balances = await self.exchange.get_balances()
prices = await self.exchange.get_prices(list(self.target_weights.keys()))
current_weights = self.calculate_weights(balances, prices)
if not self.should_rebalance_now(current_weights):
logger.info("Ребалансування не потрібне")
return RebalanceReport(skipped=True)
trades = self.calculator.calculate_trades(balances, prices, self.target_weights)
optimized_trades = self.calculator.optimize_trade_order(trades)
executed = []
for trade in optimized_trades:
try:
order = await self.exchange.place_market_order(
symbol=f"{trade.asset}/USDT",
side=trade.side,
quantity=float(trade.quantity)
)
executed.append({
'trade': trade,
'fill_price': order.fill_price,
'actual_quantity': order.filled_quantity,
'fee': order.fee
})
logger.info(f"Ребалансовано {trade.asset}: {trade.side} {trade.quantity:.4f}")
except Exception as e:
logger.error(f"Не вдалося виконати сділку {trade.asset}: {e}")
return RebalanceReport(
executed_trades=executed,
portfolio_before=current_weights,
total_cost_usd=sum(t['fee'] for t in executed),
timestamp=datetime.utcnow()
)
Цільові аллокації
Приклад конфігурації
portfolio:
target_weights:
BTC: 40
ETH: 30
SOL: 15
BNB: 10
USDT: 5 # кеш-подушка
rebalancing:
strategy: hybrid
threshold_percent: 5.0
check_interval_hours: 24
min_trade_usd: 20
# Часові обмеження
rebalance_window:
days: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday']
hours_utc: [8, 20] # тільки з 8 до 20 UTC
risk:
max_single_trade_percent: 20 # не більше 20% портфеля за раз
slippage_tolerance: 0.5
Динамічні ваги
Продвинутий варіант: ваги змінюються в залежності від рыночних умов:
def calculate_dynamic_weights(market_data: dict) -> dict:
"""
Risk-parity: розподіляємо вагу обернено пропорціонально волатильності.
Активи з меншою волатильністю отримують більший вага.
"""
assets = ['BTC', 'ETH', 'SOL', 'BNB']
volatilities = {a: market_data[a]['vol_30d'] for a in assets}
# Обернена волатильність
inv_vol = {a: 1 / v for a, v in volatilities.items()}
total_inv_vol = sum(inv_vol.values())
weights = {a: inv_vol[a] / total_inv_vol * 100 for a in assets}
return weights
Податкові розраховування
Кожна ребалансування — це податковою подія в більшості юрисдикцій (реалізація capital gain або loss). При високій частоті ребалансувань податкові зобов'язання можуть перевищити вигоду від ребалансування.
Оптимізація: ребалансувати через збиткові позиції (tax-loss harvesting) — продавати збиткові активи, перекладаючи в растучие. Автоматизація tax-loss harvesting — окрема та вмст потребувана функція для податкових юрисдикцій з деталізованим crypto reporting (США, Німеччина).
Ребалансуючий бот — це дисципліна, автоматизована в код. Дослідження показують: систематична ребалансування раз на квартал перевищує buy-and-hold на 0.5-2% в рік на волатільних активах за рахунок rebalancing premium.







