Разработка системы расчёта NAV (Net Asset Value) крипто-фонда
NAV = (Стоимость активов − Обязательства) / Количество долей фонда. Формула простая. Сложность — в том, что для крипто-фонда каждое слагаемое требует нетривиальных технических решений. Стоимость активов меняется каждую секунду, активы могут быть заперты в DeFi-протоколах, обязательства включают начисленные но не оплаченные комиссии управляющего, а количество долей меняется при подписках и погашениях. Система, которая пересчитывает NAV раз в сутки с данными от CoinGecko — не считается production-ready для регулируемого фонда.
Источники цен и проблема манипулируемости
Выбор ценового источника — не технический, а методологический вопрос с техническими последствиями. Три категории источников с разными характеристиками:
Centralized exchange (CEX) цены
Binance, Coinbase, Kraken API дают real-time цены с задержкой < 100мс. Проблемы:
- API rate limits при большом количестве активов
- Расхождение цен между биржами (arbitrage window)
- Риск манипуляции на менее ликвидных парах
- Zависимость от 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
Подписки и погашения (subscriptions/redemptions)
При входе инвестора в фонд (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 недель.







