Documentation Index
Fetch the complete documentation index at: https://docs.raydium.io/llms.txt
Use this file to discover all available pages before exploring further.
Эта страница переведена с помощью ИИ. За эталон принимается английская версия.Открыть английскую версию →
Представление sqrt-цены
CLMM хранит цену какsqrt_price_x64 — квадратный корень из цены token1 за token0, представленный в виде числа с фиксированной точкой Q64.64:
где p = token1_amount / token0_amount. Работа с sqrt вместо p линеаризует математику swap (дельты объемов токенов становятся линейны по отношению к Δsqrt_price), а фиксированная точка x64 обеспечивает точность при многотиковых swaps.
Преобразование между тиком и sqrt-ценой предвычисляется через логарифмическую аппроксимацию bit-by-bit:
реализованное как экспоненциация с таблицей поиска в tick_math::get_sqrt_price_at_tick.
Ликвидность как канонический блок
Внутри диапазона[sqrt_a, sqrt_b] (где sqrt_a < sqrt_b) позиция с ликвидностью L соответствует объемам токенов следующим образом. Пусть sqrt_c = sqrt_price_x64 — текущая цена пула.
| Случай | amount0 | amount1 |
|---|---|---|
sqrt_c <= sqrt_a (цена пула ниже диапазона) | L · (sqrt_b - sqrt_a) / (sqrt_a · sqrt_b) | 0 |
sqrt_a < sqrt_c < sqrt_b (в диапазоне) | L · (sqrt_b - sqrt_c) / (sqrt_c · sqrt_b) | L · (sqrt_c - sqrt_a) |
sqrt_c >= sqrt_b (цена пула выше диапазона) | 0 | L · (sqrt_b - sqrt_a) |
x = L / sqrt_p, y = L · sqrt_p, который сосредоточенная ликвидность удовлетворяет внутри диапазона.
Интеграторам обычно нужно обратное преобразование: по депозиту amount0 / amount1 вычислить максимальное L, которое вмещается в диапазон. SDK-функция LiquidityMath.getLiquidityFromTokenAmounts делает это. Формула для случая с диапазоном:
Какая сторона ограничена, определяет реально потребленное соотношение; другая сторона может иметь остаток.
Шаг swap в одном тике
Swap состоит из шагов. Каждый шаг либо (a) потребляет всю доступную входную ликвидность внутри текущего диапазона тика без пересечения тика, либо (b) перемещает цену ровно до следующего инициализированного тика. Имея текущее состояние(sqrt_c, L) и восходящий swap (token0 входит, token1 выходит, sqrt_price возрастает), расстояние до следующего инициализированного тика — sqrt_t. Внутри этого микроинтервала взаимосвязь между входом и ценой:
и
Программа делает одно из двух:
-
Весь ввод вмещается? Если оставшийся вводимый объем (после комиссии) меньше
Δamount0для достиженияsqrt_t, решить уравнение для новойsqrt_c'точно: (для swap с точным входомtoken0 → token1). Swap завершается на этом шаге без пересечения тика. -
Ввод превышает
Δamount0? Установитьsqrt_c' = sqrt_t, пересечь тик (применитьliquidity_net), уменьшить оставшийся ввод наΔamount0, увеличить выход наΔamount1и повторить.
token1 → token0, цена идет вниз), формулы имеют sqrt_c и sqrt_t переставленные и обращение в другом месте.
Полная реализация на Rust находится в raydium-clmm/programs/amm/src/libraries/swap_math.rs. Логика там совпадает один-в-один с SwapMath.computeSwapStep Uniswap v3.
Комиссии на каждом шаге
Торговые комиссии берутся с входного объема на каждом шаге, по тому же соглашению, что и CPMM:L_i, которая оставалась в диапазоне на протяжении этого swap, позже сможет прочитать L_i · Δfee_growth_global / 2^{64} причитающихся токенов.
Доли protocol и fund аккумулируются в PoolState.protocol_fees_token_{0,1} и PoolState.fund_fees_token_{0,1} соответственно, идентично CPMM. Они выводятся через CollectProtocolFee / CollectFundFee.
Рост комиссий снаружи и внутри
Сложная часть учета комиссий в CLMM: позиция зарабатывает комиссии только пока цена пула внутри ее диапазона. Пул отслеживает кумулятивные комиссии глобально; позиции нужно знать кумулятивные комиссии только пока она находится внутри своего диапазона. Решение — это основанный на тиках аккумулятор. Каждый тик хранит:- Если цена пула выше этого тика (
tick_current >= this_tick),fee_growth_outside = fee_growth_global. (Все заработанное до сих пор “снаружи” — то есть ниже — этого тика относительно текущей цены.) - Иначе
fee_growth_outside = 0.
fee_growth_outside этого тика:
Инвариант, который это сохраняет: для любого тика t, fee_growth_outside(t) равен комиссиям, которые накопились пока tick_current был на противоположной стороне t.
Рост комиссий внутри диапазона [tick_lower, tick_upper] тогда выводится как:
Что хранит позиция и что она читает
PersonalPositionState хранит fee_growth_inside_0_last_x64 и fee_growth_inside_1_last_x64: значения fee_growth_inside на момент последнего обращения к позиции.
При любом последующем обращении (увеличение, уменьшение, сбор), программа:
- Вычисляет текущий
fee_growth_inside_{0,1}_x64используя формулу выше. - Вычисляет
Δ = fee_growth_inside_now − fee_growth_inside_last(модульное вычитание на u128). - Добавляет
Δ × position.liquidity / 2^{64}кtokens_fees_owed_{0,1}. - Обновляет
fee_growth_inside_lastновым значением.
CollectFees / DecreaseLiquidity, против tokens_fees_owed.
Вознаграждения
Каждый из до 3 потоков вознаграждений пула использует ту же machinery на основе growth-inside, в собственном аккумулятореreward_growth_global_x64. В момент эмиссии:
— эмиссии масштабируются обратно пропорционально активной ликвидности, так что более плотный пул платит каждой позиции пропорционально меньше в секунду, но в сумме по большему количеству позиций. Причитающееся вознаграждение на одну позицию равно
и запрашивается через CollectReward. См. /ru/products/clmm/fees.
Разобранный пример: swap с точным входом
Предположим:tick_spacing = 60sqrt_price_x64 = 1 × 2^{64}— цена = 1.0, так чтоtick_current = 0.- Активная ликвидность
L = 1_000_000 × 2^{64}. - Следующий инициализированный тик выше:
t = 60(sqrt_price_b ≈1.003004 × 2^{64}). - Ставка торговой комиссии: 500 (0.05%).
SwapBaseInput с точным входом 1,000 token0.
Шаг 1 — комиссии:
999 < 2995.5, так что весь ввод вмещается без пересечения тика.
Шаг 3 — новая цена:
sqrt_c' немного ниже sqrt_c. Заметим, что формула выше предназначена для swap token1 → token0. Пример здесь — token0 → token1, что толкает цену вверх, а не вниз — так что мы используем соответствующую форму для token0 in:
token0 → token1: sqrt_c растет вместе с ценой.)
Шаг 4 — объем выхода:
trade_fee_rate × protocol_fee_rate / 1e6 (и аналогично для fund); доля LP поступает в fee_growth_global_0_x64.
Matching лимитных ордеров во время swap
Когда шаг swap пересекает тик, содержащий открытые лимитные ордера, эти ордера потребляют ввод swap перед LP-кривой, по точной цене тика. Matching — FIFO внутри тика по когортамorder_phase.
Состояние на когорту в TickState
orders_amount и наследуют следующий order_phase; они не могут заполняться пока предыдущая когорта полностью не потреблена.
Шаг matching
Псевдокод для matching, происходящего при каждом пересечении тика во время swap:SettleLimitOrder (или DecreaseLimitOrder). Пул просто отслеживает, сколько когорты теперь заполнено через unfilled_ratio_x64. Каждый LimitOrderState хранит собственный снимок (order_phase, unfilled_ratio_x64) на момент открытия, поэтому settlement сводится к:
Взаимодействие с LP-кривой
На шаге swap, matching лимитных ордеров происходит на тике (нулевоеΔsqrt_price); потребление LP-кривой происходит между тиками. Порядок, следовательно:
- Пересечь тик
t_cross(применить изменение LPliquidity_netпервым, так как это то, как делает Uniswap-V3). - Заполнить любые лимитные ордера, сидящие на
t_cross. - Продолжить вдоль LP-кривой к следующему инициализированному тику или до исчерпания
swap_input.
Вывод динамической комиссии
PoolState.dynamic_fee_info несет состояние волатильности. Каждый шаг swap вычисляет ставку комиссии за шаг как:
где:
- —
DYNAMIC_FEE_CONTROL_DENOMINATOR - —
VOLATILITY_ACCUMULATOR_SCALE vol_acc— это per-swap аккумулятор после правила обновления нижеtick_spacingизPoolState.tick_spacing
Обновление аккумулятора
Два правила применяются каждый swap по порядку: Decay. Референтный уровень снижается на основе времени с момента последнего обновления: Accumulate. Новый аккумулятор — это референс плюс расстояние в тиках, пройденное с предыдущего референсного индекса:tick_spacing_index_reference () в единицах tick-spacing, не в сырых тиках: .
Почему парабола от расстояния в тиках
Возведение аккумулятора в квадрат означает, что комиссия растет как квадрат расстояния, на которое цена отошла от своей референтной точки. Эмпирически это соответствует масштабированию дисперсии цены при случайном блуждании: прогулка на 2× расстояния в тиках подразумевает в 4 раза больше подразумеваемую волатильность, так что взимает в 4 раза больше надбавку. Параметрdynamic_fee_control калибрует абсолютный уровень.
Окно filter_period предотвращает крошечные субсекундные колебания (например, MEV-боты, выполняющие sandwich-атаки) от раздувания аккумулятора. Окно decay_period предотвращает один прошлый скачок от взимания комиссий неопределенно долго после того, как рынок успокоился.
Численная устойчивость
- Все промежуточные произведения проходят через арифметику
u128илиu256. CLMM использует хелперыU128Sqrtи паттерныFullMath::mulDivнепосредственно перенесенные из Uniswap v3. - Округление при делении выбирается по шагам, чтобы обеспечить инвариант
k' ≥ kлокально.SwapBaseInputокругляет выход вниз;SwapBaseOutputокругляет ввод вверх. - Пересечения тиков, которые опускают
PoolState.liquidityна нуль, разрешены (цена может пройти через “дыру ликвидности”), но swap просто переходит к следующему инициализированному тику без потребления ввода, не взимая комиссию. - Защита от переполнения:
sqrt_price_x64держится в включающем диапазоне[MIN_SQRT_PRICE_X64, MAX_SQRT_PRICE_X64], соответствующем[MIN_TICK, MAX_TICK]. Swap, который попытается выйти за границы, вернет ошибкуSqrtPriceLimitOverflow.
Куда идти дальше
/ru/products/clmm/ticks-and-positionsо том, как карта тиков участвует в обходе./ru/products/clmm/feesдля деталей математики комиссий/вознаграждений./ru/algorithms/clmm-mathдля выводов позадиL = sqrt(x · y)и формул диапазона-к-ликвидности.
raydium-io/raydium-clmm—libraries/swap_math.rs,libraries/tick_math.rs- Whitepaper “Uniswap v3 Core”, §6–7


