Разработка бота для ребалансировки портфеля
Ребалансирующий бот поддерживает заданную аллокацию активов в портфеле. Если 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("Rebalancing not needed")
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"Rebalanced {trade.asset}: {trade.side} {trade.quantity:.4f}")
except Exception as e:
logger.error(f"Failed to execute {trade.asset} trade: {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.







