Разработка системы rebalancing портфеля фонда
Управление крипто-портфелем фонда — это не просто "купить/продать по рынку". Между целевыми весами активов и исполнением сделок стоит целый комплекс проблем: проскальзывание на неликвидных активах, налоговые последствия каждой сделки, MEV-атаки, соблюдение инвестиционного мандата, аудиторский след. Система ребалансировки для фонда — это не торговый бот, это back-office инфраструктура с жёсткими требованиями к надёжности и прозрачности.
Декомпозиция задачи
Система ребалансировки состоит из нескольких независимых подсистем, которые нужно проектировать отдельно:
- Portfolio state engine — отслеживает текущие позиции с учётом всех источников (on-chain, CEX, DeFi позиции в протоколах)
- Rebalancing trigger — определяет, когда нужно ребалансировать
- Order computation engine — вычисляет минимальный набор сделок для достижения целевых весов
- Execution layer — исполняет сделки оптимально с точки зрения цены и market impact
- Risk & compliance layer — проверки перед исполнением
- Audit trail — полная история всех решений и исполнений
Portfolio State Engine
Первая и самая недооцениваемая часть. Фонд держит активы в разных местах — это фундаментальная сложность.
Источники данных о позициях:
| Источник | Метод получения | Latency |
|---|---|---|
| On-chain ERC-20 балансы | eth_call на balanceOf(address) |
Блок (~12с) |
| Uniswap V3 LP позиции | NonfungiblePositionManager.positions(tokenId) |
Блок |
| Aave/Compound lending | aToken.balanceOf / borrowBalanceStored |
Блок |
| Binance/Coinbase CEX | REST API с подписью HMAC | <1с |
| Staking (Lido, validators) | Beacon Chain API + stETH balance | Блок |
DeFi позиции — самая сложная часть. Liquidity position в Uniswap V3 — это не просто "X токенов ETH + Y токенов USDC". Реальные суммы зависят от текущей цены и range: если цена вышла за пределы range — позиция на 100% в одном активе. Нужно честно считать через SDK:
import { Pool, Position } from '@uniswap/v3-sdk';
import { Token, CurrencyAmount } from '@uniswap/sdk-core';
function getLiquidityAmounts(position: Position, currentPrice: Price) {
const { amount0, amount1 } = position.mintAmounts;
// Возвращает реальные amounts с учётом текущего tick
return { token0Amount: amount0, token1Amount: amount1 };
}
Ценообразование позиций. Для расчёта весов нужны цены в USD. Проблемы:
- Неликвидные токены: spot price из тонкого стакана не репрезентативна. Использовать TWAP (Uniswap V3 30-min TWAP) или geometric mean of multiple DEX prices
- Yield-bearing assets (stETH, aUSDC): учитывать accumulated yield
- LP позиции: impermanent loss меняет эффективную стоимость
Rebalancing Triggers
Threshold-based — ребалансировка когда вес актива отклонился от целевого более чем на X%. Например: цель ETH = 40%, ребалансировка при весе < 36% или > 44%. Простой подход, но может генерировать много небольших сделок при волатильном рынке.
Periodic — ребалансировка раз в N дней/недель независимо от дрейфа. Предсказуемо с точки зрения налогов и operational overhead.
Hybrid — ребалансировка при пересечении threshold, но не чаще чем раз в X дней. Практически наиболее распространён.
Cost-aware rebalancing. Ребалансировка стоит денег: gas, spread, slippage, налоги с прибыли (в некоторых юрисдикциях). Математически ребалансировку следует проводить, только если ожидаемое улучшение risk-adjusted доходности от восстановления целевых весов превышает стоимость сделок:
def should_rebalance(current_weights, target_weights, trade_costs) -> bool:
# Tracking error = sqrt(sum((w_i - t_i)^2))
tracking_error = np.sqrt(np.sum((current_weights - target_weights) ** 2))
estimated_trade_volume = compute_trade_volume(current_weights, target_weights)
total_cost = estimated_trade_volume * trade_costs.avg_cost_bps / 10000
# Ребалансируем если drift достаточно большой относительно стоимости
return tracking_error > THRESHOLD and tracking_error / total_cost > MIN_BENEFIT_COST_RATIO
Order Computation Engine
Цель: найти минимальный набор сделок, переводящий портфель из текущих весов в целевые, с минимальными транзакционными издержками.
Naive подход: для каждого актива вычислить delta и торговать парой с USDC/USDT. Проблема: создаёт лишние сделки. Если уменьшаем ETH и увеличиваем BTC — зачем делать ETH→USDC и USDC→BTC? Лучше найти прямую пару ETH→BTC если существует ликвидный рынок.
Оптимизация через сеть потоков (flow network):
import networkx as nx
from scipy.optimize import linprog
def optimize_rebalance_trades(current_usd, target_usd, available_pairs):
"""
current_usd: dict {token: usd_value}
target_usd: dict {token: usd_value}
available_pairs: list[(token_a, token_b, cost_bps)]
"""
deltas = {t: target_usd[t] - current_usd.get(t, 0) for t in target_usd}
# Строим граф торговых пар, ищем min cost flow
G = nx.DiGraph()
for a, b, cost in available_pairs:
G.add_edge(a, b, weight=cost)
G.add_edge(b, a, weight=cost)
# Решаем как транспортную задачу
# ... (LP solver)
return optimal_trades
Учёт market impact. Крупные сделки двигают цену против себя. Для фонда с $10M+ AUM нельзя исполнять как один ордер. Модели market impact:
-
Square-root model:
impact = σ * sqrt(Q / ADV), где ADV — average daily volume - Almgren-Chriss model — более точная модель для TWAP исполнения
Execution Layer
CEX исполнение. Для ликвидных активов на Binance/Coinbase: TWAP или VWAP ордера через API. TWAP разбивает крупный ордер на части равными интервалами, минимизируя market impact.
async def execute_twap(exchange, symbol, side, total_qty, duration_minutes, slices=10):
slice_qty = total_qty / slices
interval = duration_minutes * 60 / slices
for i in range(slices):
order = await exchange.create_order(
symbol=symbol,
type='market', # или limit с небольшим offset
side=side,
amount=slice_qty
)
log_execution(order, slice=i)
await asyncio.sleep(interval)
On-chain исполнение через агрегаторы. 1inch, Paraswap, 0x Protocol — агрегируют ликвидность с DEX для получения лучшей цены:
import axios from 'axios';
import { ethers } from 'ethers';
async function executeSwapVia1inch(
tokenIn: string,
tokenOut: string,
amount: bigint,
slippageBps: number
) {
const quote = await axios.get(`https://api.1inch.dev/swap/v6.0/1/swap`, {
params: {
src: tokenIn,
dst: tokenOut,
amount: amount.toString(),
from: FUND_ADDRESS,
slippage: slippageBps / 100,
},
headers: { Authorization: `Bearer ${ONEINCH_API_KEY}` }
});
const tx = await signer.sendTransaction({
to: quote.data.tx.to,
data: quote.data.tx.data,
value: quote.data.tx.value,
});
return tx;
}
MEV защита для on-chain сделок. Крупные своп-транзакции фонда видны в мемпуле и привлекают сэндвич-атаки. Решения:
-
Flashbots Protect RPC (
https://rpc.flashbots.net) — транзакции идут в private mempool, не видны MEV-ботам до включения в блок - MEV Blocker от CoW Protocol — аналогичная функциональность
- CoW Protocol (Coincidence of Wants) — batch auctions, встроенная MEV protection
Risk & Compliance Layer
Обязательные проверки перед исполнением любой сделки:
class RiskChecks:
def pre_trade_checks(self, proposed_trades: list[Trade]) -> list[CheckResult]:
checks = [
self.check_position_limits(proposed_trades), # лимиты на актив
self.check_concentration_risk(proposed_trades), # диверсификация
self.check_liquidity_impact(proposed_trades), # market impact
self.check_counterparty_limits(proposed_trades), # лимиты на биржу
self.check_blacklisted_assets(proposed_trades), # санкционные списки
self.check_investment_mandate(proposed_trades), # ограничения мандата
]
return checks
Investment mandate enforcement. Фонды работают с ограничениями: "не более 20% в одном активе", "только активы с market cap > $500M", "не более 30% в DeFi протоколах". Система должна проверять мандат до исполнения и блокировать нарушения.
Audit Trail и отчётность
Каждое решение о ребалансировке должно быть задокументировано: что было триггером, какие веса были до/после, какие сделки были исполнены, по какой цене, сколько стоило.
CREATE TABLE rebalancing_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
triggered_at TIMESTAMPTZ NOT NULL,
trigger_type VARCHAR(50), -- 'threshold', 'periodic', 'manual'
weights_before JSONB,
weights_target JSONB,
weights_after JSONB,
total_trade_volume_usd NUMERIC,
total_cost_usd NUMERIC,
execution_status VARCHAR(50),
trades JSONB -- массив исполненных сделок
);
Это нужно не только для внутреннего контроля — аудиторы и регуляторы потребуют полный trail.
Технологический стек и архитектура
Рекомендованный стек:
| Компонент | Технология |
|---|---|
| Backend | Python (pandas/numpy для расчётов весов, asyncio для параллельного исполнения) |
| Планировщик | Apache Airflow или Prefect (DAG для rebalancing workflow) |
| Хранилище | PostgreSQL + TimescaleDB (time-series позиций) |
| CEX подключения | CCXT (унифицированный интерфейс к 100+ биржам) |
| On-chain | viem/ethers.js + Flashbots bundle |
| Мониторинг | Grafana + Prometheus + PagerDuty алерты |
| Secrets | HashiCorp Vault для API ключей и приватных ключей |
Безопасность ключей критична. Система управляет реальными средствами фонда. Приватные ключи — никогда в конфигурационных файлах или переменных окружения контейнеров. Схема: HSM (Hardware Security Module) или KMS (AWS KMS / GCP KMS) для хранения ключей, подписание транзакций через secure enclave, multi-sig для операций выше порогового размера.
Сроки и этапы
Этап 1 — Portfolio tracking (3-4 недели). Агрегация позиций из всех источников, ценообразование, расчёт весов. Это основа всей системы.
Этап 2 — Rebalancing engine (2-3 недели). Триггеры, оптимизация сделок, risk checks.
Этап 3 — Execution layer (3-4 недели). CEX интеграции через CCXT, on-chain исполнение с MEV защитой, TWAP логика.
Этап 4 — Мониторинг и compliance (2 недели). Audit trail, отчёты, алерты, панель мониторинга.
Итого: 2.5-3.5 месяца для production-готовой системы. Ускорение возможно за счёт ограничения количества поддерживаемых источников позиций.







