Розробка системи розрахунку funding rate для perpetual DEX
Perpetual futures — найбільший за обсягом інструмент у крипто. Binance обробляє $20-50 мільярдів notional на день тільки в BTC perp. Механізм funding rate — те, що утримує ціну perpetual близько до spot. Без нього perp міг би торгуватися з постійною 50% премією над spot, роблячи хеджування марним. На CEX це вирішено централізовано. На децентралізованому perp DEX — потрібна повністю on-chain система, стійка до маніпуляцій.
Механіка funding rate: що саме ми розраховуємо
Класична формула (Bitmex style)
Базова формула funding rate, яку використовує більшість CEX:
Funding Rate = clamp(Premium Index + clamp(IR - Premium Index, -0.05%, 0.05%), -0.075%, 0.075%)
де:
- Premium Index = (Mark Price - Index Price) / Index Price
- IR (Interest Rate) = зазвичай фіксована 0.01% за 8h у крипто
- clamp обмежує значення діапазоном
Mark Price — середньозважена ціна з урахуванням обсягу з кількох майданчиків. Index Price — spot ціна від оракула (Chainlink / Pyth).
Коли Mark Price > Index Price (perp торгується з премією) — funding rate позитивний, лонги платять шортам. Ринок стимулюється відкривати шорти і закривати лонги, натискаючи ціну perp вниз до spot.
Проблема маніпуляції Mark Price
На on-chain perp DEX Mark Price не може просто бути останньою ціною угоди — тривіально маніпулюєтся через flash loan або wash trading у маленькому пулі. Атакуючий виконує серію великих угод перед снимком funding rate → завищує Mark Price → funding rate на його користь.
Захист через TWAP (Time-Weighted Average Price). Mark Price розраховується як середньозважена ціна за період (зазвичай 1-8 годин):
function getMarkPrice() public view returns (uint256) {
uint256 twapPrice = 0;
uint256 totalWeight = 0;
for (uint i = 0; i < observations.length; i++) {
uint256 weight = observations[i].timestamp - (i > 0 ? observations[i-1].timestamp : periodStart);
twapPrice += observations[i].price * weight;
totalWeight += weight;
}
return totalWeight > 0 ? twapPrice / totalWeight : currentPrice;
}
Uniswap V3 використовує подібний підхід з функцією observe(), яка повертає累積value ticks. Довгий період TWAP (8h) робить маніпуляцію дорогою: потрібно утримувати штучну ціну протягом усього періоду.
Pyth Network проти Chainlink для Index Price
On-chain perp DEX вимагає низької затримки для Index Price. Chainlink оновлюється при відхиленні >0.5% або кожні heartbeat (1h на основних парах) — занадто повільно на швидкому ринку. Pyth забезпечує оновлення цін кожні 400ms через pull-based модель.
Pyth pull oracle — контракт не зберігає ціну постійно, замість цього користувач або keeper передає VAA (Verifiable Action Approval) в кожній транзакції:
function updateAndGetPrice(bytes[] calldata priceUpdateData) external payable returns (PythStructs.Price memory) {
uint fee = pyth.getUpdateFee(priceUpdateData);
pyth.updatePriceFeeds{value: fee}(priceUpdateData);
return pyth.getPriceUnsafe(priceId); // або getPrice для перевірки застарілості
}
Компроміс: кожна транзакція несе невеликі витрати на газ для оновлення ціни. Для perp DEX це прийнятно — точність важливіша.
Нарахування funding rate на блокчейні
Неперервне проти дискретного нарахування
CEX нараховують funding rate дискретно — кожні 8 годин. Для протоколу on-chain два підходи:
Discrete (snapshot): Keeper викликає settleFunding() кожні 8 годин. Всі позиції оновлюються в одній транзакції. Проблема: при великій кількості позицій — перевищена межа газу. Рішення: розбита на сторінки settlement або lazy settlement (нарахування при наступній взаємодії користувача з позицією).
Continuous (per-block accumulation): Використовується в dYdX v3 та Synthetix. Замість settlement один раз за період — fundingIndex зростає неперервно з кожним блоком. При відкритті позиції запам'ятовуємо entryFundingIndex. При закритті — (currentFundingIndex - entryFundingIndex) * positionSize = funding payment.
mapping(address => uint256) public positionEntryFundingIndex;
uint256 public globalFundingIndex;
function calculateFundingPayment(address trader) public view returns (int256) {
return int256(positionSize[trader]) *
int256(globalFundingIndex - positionEntryFundingIndex[trader]) / 1e18;
}
Неперервний підхід більш елегантний і масштабується до будь-якої кількості позицій без розбиття на сторінки. Головне: globalFundingIndex повинен оновлюватися регулярно (Chainlink Automation або власний keeper).
Реалізація з врахуванням знакових позицій
Лонг-позиції та короткі позиції платять/отримують funding у протилежних напрямках. Стандартний підхід: зберігати signed position size (позитивна для лонга, негативна для шорта), funding payment автоматично змінює знак:
// Позитивний funding rate → лонги платять, шорти отримують
// Негативний funding rate → шорти платять, лонги отримують
int256 fundingPayment = signedPositionSize * int256(fundingRateDelta) / 1e18;
// Якщо signedPositionSize > 0 (лонг) та fundingRate > 0 → payment < 0 (платимо)
// Якщо signedPositionSize < 0 (шорт) та fundingRate > 0 → payment > 0 (отримуємо)
Межі funding rate та екстремальні ринки
При екстремальному дисбалансі на ринку (99% учасників — лонги) funding rate без обмежень може стати астрономічно високим. Шорти отримували б величезні виплати, але ніхто не хоче бути шортом в сильному бичачому тренді.
Потрібні межі:
-
maxFundingRateза період — запобігає екстремальним виплатам - Ступенева ставка: малий дисбаланс — низька ставка, великий дисбаланс — зростає нелінійно
GMX v2 використовує адаптивний funding rate: більший дисбаланс відкритого інтересу — швидша зміна funding rate. М'якше, ніж hard cap, ефективніше коригує дисбаланс.
Інфраструктура Keeper
Система funding rate вимагає регулярного оновлення on-chain. Варіанти:
Chainlink Automation. Надійно, децентралізовано, але затримка не гарантована при навантаженні на мережу.
Gelato Network. Аналог Chainlink Automation, додаткові опції для умовних тригерів.
Власний keeper. Повний контроль над затримкою, але вимагає інфраструктури. Для критичного фінансового протоколу — рекомендуємо власний keeper з Chainlink як резервний варіант.
// Логіка Keeper
async function updateFunding() {
const lastUpdate = await contract.lastFundingUpdate();
if (Date.now() / 1000 - lastUpdate > FUNDING_INTERVAL) {
const markPrice = await getMarkPriceTWAP();
const indexPrice = await pythOracle.getPrice(PRICE_ID);
await contract.updateFundingRate(markPrice, indexPrice);
}
}
setInterval(updateFunding, 60_000); // перевірка кожну хвилину
Процес розробки
Аналітика (2-3 дні). Вибір формули (Bitmex style, адаптивна, обмежена), стратегія оракула, модель settlement (discrete/continuous).
Розробка (3-5 днів). Контракт FundingRateEngine: розрахунок Mark Price TWAP, інтеграція Pyth/Chainlink для Index Price, неперервний funding index, логіка settlement.
Тестування (2-3 дні). Fork-тести з симуляцією різних умов на ринку: екстремальний бичачий (99% long OI), flash crash, швидкі зміни funding rate. Fuzz-тести на інваріант: платежі funding повинні збалансуватися (сума платежів лонгів = сума отримань шортів при нульовому insurance fund).
Deploy Keeper. Setup Chainlink Automation або власний сервіс keeper.
Орієнтири за часом
Базова система з discrete settlement та Chainlink oracle — 3-4 дні. Неперервне funding з Pyth, адаптивними межами та інфраструктурою keeper — 1-2 тижні. Вартість розраховується індивідуально.







