Розроблення системи 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 баланс | Блок |
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) або геометричне середнє multiple DEX цін
- Yield-bearing assets (stETH, aUSDC): врахувати накопичений yield
- LP позиції: impermanent loss змінює ефективну вартість
Rebalancing Triggers
Threshold-based — ребалансування коли вага активу відхилилася від цільової більш ніж на X%. Наприклад: ціль ETH = 40%, ребалансування при вазі < 36% або > 44%. Простий підхід, але може генерувати багато дрібних сделок при волатильному ринку.
Periodic — ребалансування раз на N днів/тижнів незалежно від driftu. Передбачуване з точки зору податків та 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 модель:
impact = σ * sqrt(Q / ADV), де ADV — average daily volume - Almgren-Chriss модель — точніша для 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 сделок. Великі своп-транзакції фонду видні в мемпулі та привертають sandwich-атаки. Рішення:
-
Flashbots Protect RPC (
https://rpc.flashbots.net) — транзакції йдуть у private mempool, не видні MEV-ботам до включення в блок - MEV Blocker від CoW Protocol — аналогічна функціональність
- CoW Protocol (Coincidence of Wants) — batch auctions, вбудована MEV защита
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-готової системи. Прискорення можливо за рахунок обмеження кількості підтримуваних джерел позицій.







