Розробка системи розрахунку NAV крипто-фонду
NAV = (Вартість активів − Зобов'язання) / Кількість долей фонду. Формула проста. Складність — у тому, що для крипто-фонду кожен компонент вимагає нетривіальних технічних рішень. Вартість активів змінюється щосекунди, активи можуть бути заблоковані в DeFi-протоколах, зобов'язання включають нараховані але невиплачені комісії управління, а кількість долей змінюється при підписках та погашеннях. Система, яка перераховує NAV раз на день з даними від CoinGecko — не вважається production-ready для регульованого фонду.
Джерела цін та проблема маніпульованості
Вибір ціневого джерела — не технічне, а методологічне питання з технічними наслідками. Три категорії джерел з різними характеристиками:
Centralized Exchange (CEX) ціни
Binance, Coinbase, Kraken API дають real-time ціни з затримкою < 100мс. Проблеми:
- API rate limits при великій кількості активів
- Розходження цін між біржами (arbitrage window)
- Ризик маніпуляції на менш ліквідних парах
- Залежність від uptime біржи
Для розрахунку офіційного NAV (T-1 або T-0) краще використовувати VWAP (Volume-Weighted Average Price) за останні 1–4 години торгів на топ-3 біржах за ліквідністю пари:
def calculate_vwap(trades: List[Trade], window_hours: int = 1) -> Decimal:
cutoff = datetime.utcnow() - timedelta(hours=window_hours)
recent = [t for t in trades if t.timestamp >= cutoff]
total_volume = sum(t.volume for t in recent)
if total_volume == 0:
return recent[-1].price if recent else Decimal('0')
return sum(t.price * t.volume for t in recent) / total_volume
On-chain oracle ціни
Chainlink Price Feeds — децентралізовані оракули з агрегацією від професійних data providers. Оновлюються при відхиленні > 0.5% або раз в heartbeat (1 година). Для розрахунку NAV on-chain — єдиний прийнятний варіант.
Uniswap V3 TWAP — Time-Weighted Average Price з pool observation buffer. Стійкий до flash loan маніпуляцій, але вразливий до sustained manipulation на малоліквідних пулах:
function getTWAP(address pool, uint32 period) external view returns (uint256 price) {
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = period; // наприклад, 1800 = 30 хвилин
secondsAgos[1] = 0;
(int56[] memory tickCumulatives,) = IUniswapV3Pool(pool).observe(secondsAgos);
int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
int24 arithmeticMeanTick = int24(tickCumulativesDelta / int56(uint56(period)));
price = TickMath.getSqrtRatioAtTick(arithmeticMeanTick);
// перетворення з sqrtPriceX96 в decimal...
}
Composited pricing pipeline
Для production фонду рекомендується багаторівнева схема:
Primary: Chainlink / CoinGecko CoinAPI
Fallback1: CEX VWAP (Binance + Coinbase + Kraken weighted by volume)
Fallback2: On-chain TWAP (Uniswap V3, 30min window)
Stale: Last known price + staleness flag + manual override required
Облік DeFi-позицій
Це найскладніша частина для крипто-фонду з активною DeFi-стратегією. Активи фонду можуть бути одночасно:
- У liquidity positions Uniswap V3 (concentrated liquidity, not fungible)
- В Aave/Compound як collateral (з нарахованими відсотками)
- У lending позиціях (борг, який потрібно вичитати)
- У стейкингу з vesting/lock-up (неможливо продати миттєво)
- У Curve/Convex як LP токени (multi-asset pools)
Uniswap V3 позиції
Позиції V3 — NFT (ERC-721). Вартість позиції залежить від поточної ціни пула та ціновому діапазону:
async function getUniswapV3PositionValue(
positionId: number,
priceUSD: Record<string, number>
): Promise<number> {
const position = await positionManager.positions(positionId);
const pool = await getPool(position.token0, position.token1, position.fee);
const { amount0, amount1 } = getAmountsForLiquidity(
pool.sqrtPriceX96,
getSqrtRatioAtTick(position.tickLower),
getSqrtRatioAtTick(position.tickUpper),
position.liquidity
);
// Плюс накопленные fees
const { fees0, fees1 } = await collectableFees(positionId);
return (
(amount0 + fees0) * priceUSD[position.token0] +
(amount1 + fees1) * priceUSD[position.token1]
);
}
Aave позиції
Aave повертає дані через getUserAccountData(address):
(
uint256 totalCollateralBase, // в USD * 10^8
uint256 totalDebtBase, // в USD * 10^8
uint256 availableBorrowsBase,
uint256 currentLiquidationThreshold,
uint256 ltv,
uint256 healthFactor
) = aaveLendingPool.getUserAccountData(fundAddress);
int256 netAavePosition = int256(totalCollateralBase) - int256(totalDebtBase);
Облік комісій управління
Структура комісій типичного хедж-фонду (2/20 model):
- Management fee: 2% річних від NAV, нараховується daily (2% / 365 = 0.00548% на день)
- Performance fee: 20% від приросту NAV вище High Water Mark (HWM)
Нараховані але не виплачені комісії — liability фонду, яка зменшує NAV.
class NAVCalculator:
def calculate_daily_management_fee(self, nav: Decimal, date: date) -> Decimal:
daily_rate = Decimal('0.02') / Decimal('365')
return nav * daily_rate
def calculate_performance_fee(
self,
current_nav_per_share: Decimal,
high_water_mark: Decimal
) -> Decimal:
if current_nav_per_share <= high_water_mark:
return Decimal('0')
gain_above_hwm = current_nav_per_share - high_water_mark
return gain_above_hwm * Decimal('0.20')
def calculate_nav(self, gross_assets: Decimal, accrued_fees: Decimal) -> Decimal:
return gross_assets - accrued_fees
Підписки та погашення
При входженні інвестора в фонд (subscription) випускаються нові доли по NAV на дату підписки. При виході (redemption) — доли гасяться по NAV на дату погашення. Це вимагає атомарних операцій:
T+0: Інвестор переводить USDC → fund wallet
T+0: Створюється pending subscription запис
T+1: Розраховується офіційний NAV (cut-off time)
T+1: Випускаються доли: shares = usdcAmount / navPerShare
T+2: Інвестор отримує підтвердження та запис у реєстрі
Share registry може бути on-chain (ERC-20, ERC-1400 для security tokens) або off-chain в традиційному transfer agent. Для регульованих фондів — ERC-1400/ST-20 з KYC/AML модулями та transfer restrictions.
Аудит та reconciliation
Щоденний NAV повинен бути воспроізводимим та аудируємим. Архітектурне вимога: всі дані, використані при розрахунку NAV, повинні бути збережені:
CREATE TABLE nav_calculation_snapshots (
id UUID PRIMARY KEY,
calculation_date DATE NOT NULL,
fund_id UUID NOT NULL,
nav_per_share NUMERIC(28, 10) NOT NULL,
total_nav NUMERIC(28, 10) NOT NULL,
total_shares NUMERIC(28, 10) NOT NULL,
price_sources JSONB NOT NULL, -- всі ціни з джерелами
positions_snapshot JSONB NOT NULL, -- стан кожної позиції
accrued_fees NUMERIC(28, 10) NOT NULL,
calculated_at TIMESTAMPTZ NOT NULL,
calculated_by VARCHAR(100) NOT NULL,
UNIQUE(fund_id, calculation_date)
);
Незалежна верифікація NAV (fund administrator) повинна мати доступ до тих же сирих даних. Розходження > 0.1% вимагає ручного розслідування.
Архітектура системи
Data collection layer: Go сервіси для сбору цін (CEX websockets + oracle polling) з записом в TimescaleDB. Частота сбору — щохвилини для топ-активів.
NAV calculation engine: Python (numpy/pandas) для фінансової математики, запускається по розкладу та on-demand. Окремі розрахункові машини для кожного типу позиції (spot, DeFi LP, staking).
Admin dashboard: React + дані через REST API. Відображення breakdown NAV по позиціям, історія NAV, pending subscriptions/redemptions.
Reporting: автоматична генерація NAV reports в PDF для інвесторів та регуляторів.
Розробка системи для фонду з 10–20 активами та базовими DeFi позиціями (Aave, Uniswap) — 8–12 тижнів. Підтримка складних стратегій (опціони, структуровані продукти, cross-chain) вимагає додатково 4–8 тижнів.







