> ## 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.

# Тики и позиции

> Как CLMM квантует цену в тики, как массивы тиков компактно хранят данные, и какие правила определяют, где может находиться позиция.

<Info>
  **Эта страница переведена с помощью ИИ. За эталон принимается английская версия.**

  [Открыть английскую версию →](/products/clmm/ticks-and-positions)
</Info>

## Зачем нужны тики

Ликвидность в CLMM сосредоточена в диапазонах цен. Чтобы работать с диапазонами на цепи, цены квантуются в целые числа — **тики**, где каждый тик представляет постоянный множитель от предыдущего:

$$
\text{price}(i) = 1.0001^{\,i}
$$

Один тик соответствует движению цены на 0,01%, или примерно 1 базисный пункт. Соответствие выглядит так:

| Индекс тика `i`      | Множитель цены      |
| -------------------- | ------------------- |
| `0`                  | `1.0000`            |
| `100`                | `1.0100` (≈ +1.00%) |
| `-100`               | `0.9900` (≈ −0.99%) |
| `10000`              | `2.7181` (≈ e)      |
| `MAX_TICK = 443636`  | ≈ `1.84e19`         |
| `MIN_TICK = -443636` | ≈ `5.42e-20`        |

`MIN_TICK` и `MAX_TICK` выбраны так, чтобы `sqrt_price_x64` уместился в `u128` на обоих концах. Каждый пул требует, чтобы `tick_lower >= MIN_TICK` и `tick_upper <= MAX_TICK`. На практике веб-интерфейс ограничивает диапазон намного сильнее, чтобы предотвратить блокировку ликвидности в недостижимых тиках.

## Интервал тиков

`AmmConfig` пула устанавливает **интервал тиков** — единственные тики, которые позиция может использовать в качестве граничных точек. Если `tick_spacing = 60`, допустимы только тики `…, −120, −60, 0, 60, 120, …`. Попытка открыть позицию с граничной точкой `31` откатывается с ошибкой `InvalidTickIndex`.

Типичные публичные интервалы:

| Уровень комиссии | `trade_fee_rate` | Интервал тиков | Минимальный шаг цены на позицию |
| ---------------- | ---------------- | -------------- | ------------------------------- |
| 0.01%            | `100`            | 1              | 0.01%                           |
| 0.05%            | `500`            | 10             | 0.10%                           |
| 0.25%            | `2500`           | 60             | 0.60%                           |
| 1.00%            | `10000`          | 120            | 1.21%                           |

Чем больше интервал, тем меньше массивов тиков нужно инициализировать, тем дешевле открыть широкую позицию, но тем менее точна граница цены. Волатильные пары обычно находятся в уровнях с интервалом 120; стейблкоины — в уровнях с интервалом 1.

## Массивы тиков

Пул не хранит состояние каждого тика в отдельных аккаунтах. Вместо этого `TICK_ARRAY_SIZE` смежных тиков (60 в текущей Raydium CLMM) упакованы в один `TickArrayState`. Первый тик массива — это его `start_tick_index`, и он охватывает ровно `TICK_ARRAY_SIZE * tick_spacing` целых единиц тика.

Для `tick_spacing = 60` и `TICK_ARRAY_SIZE = 60`:

* Каждый массив тиков охватывает `60 × 60 = 3600` целых тиков.
* `start_tick_index` — это кратное 3600: `…, -7200, -3600, 0, 3600, 7200, …`.

Граничная точка позиции `t = 2040` при `tick_spacing = 60` находится в массиве тиков с `start_tick_index = 0`. Граничная точка позиции `t = 4200` находится в массиве с `start_tick_index = 3600`.

### Когда создаётся массив

Массив тиков создаётся лениво: **первая** позиция, ссылающаяся на какой-либо тик внутри него, инициализирует массив и платит за хранение. Свопы не инициализируют массивы тиков — они пропускают неинициализированные массивы, используя битовую карту. Поток открытия позиции в SDK проверяет выбранный диапазон, вычисляет список массивов тиков, которые он затрагивает, и добавляет инструкции `init_tick_array` в ту же транзакцию что и `OpenPosition`, если какие-то из них отсутствуют.

### Массивы тиков не закрываются

После инициализации массив тиков сохраняется на весь срок существования пула. Программа **не** предоставляет способ закрыть массив тиков, даже когда `initialized_tick_count` вернётся к нулю. Плата за хранение массивов тиков не возвращается; плата, уплаченная первой позицией, которая коснулась массива, остаётся заблокирована в этом аккаунте навсегда. Это намеренный компромисс: повторное использование существующего массива тиков бесплатно для каждой последующей позиции, поэтому активно торгуемый пул платит за хранение только один раз за слот `(pool, start_tick_index)`, независимо от текучести.

### Битовая карта

Поиск "следующего инициализированного тика слева/справа от текущего тика" должен быть быстрым — один свап может пересечь много тиков. Пул хранит 1-бит-на-массив-тиков битовую карту прямо в `PoolState` для диапазона ±1,024 массивов вокруг тика 0. За пределами этого диапазона (полнодиапазонные позиции, экзотические конфигурации) `TickArrayBitmapExtension` предоставляет переполнение.

Свопу прогулять битовую карту: `lowest_set_bit_above(tick_current_array_index)` даёт следующий массив с инициализированным тиком на стороне, в которую свопу движется. Внутри этого массива похожее сканирование битов находит следующий инициализированный тик.

## `liquidity_gross` и `liquidity_net`

Каждый **инициализированный** тик хранит две величины ликвидности:

* **`liquidity_gross`** — сумма `L` над всеми позициями, ссылающимися на этот тик как граничную точку. Когда `liquidity_gross` достигает нуля, тик становится неинициализированным и может быть удалён из битовой карты.
* **`liquidity_net`** — *знаковое* изменение уровня пула `liquidity` при пересечении этого тика **в направлении вверх** (слева направо в пространстве тиков). Если этот тик — нижняя граница позиции размером `L`, он добавляет `+L`; если это верхняя граница этой позиции, он добавляет `−L`.

Пример расчёта: две позиции в одном пуле.

* Позиция A: `tick_lower = -120`, `tick_upper = 0`, ликвидность `L_A = 100`.
* Позиция B: `tick_lower = -60`, `tick_upper = 60`, ликвидность `L_B = 50`.

Состояние по тикам:

| Тик    | Затронут | `liquidity_gross` | `liquidity_net` |
| ------ | -------- | ----------------- | --------------- |
| `-120` | A lower  | 100               | +100            |
| `-60`  | B lower  | 50                | +50             |
| `0`    | A upper  | 100               | −100            |
| `60`   | B upper  | 50                | −50             |

Уровень пула `liquidity` при разных значениях `tick_current`:

* `tick_current = -180`: `liquidity = 0` (до любой позиции)
* `tick_current = -90`: `liquidity = 100` (только внутри A)
* `tick_current = -30`: `liquidity = 150` (внутри A и B)
* `tick_current = 30`: `liquidity = 50` (только внутри B)
* `tick_current = 90`: `liquidity = 0` (после обеих)

При каждом пересечении тика во время свопа программа добавляет `liquidity_net` (возможно, отрицательное значение) к `PoolState.liquidity`. Это точно механизм Uniswap v3.

## Позиции как NFT

Позиция Raydium CLMM — это NFT. Открытие позиции создаёт новый минт с предложением 1 в кошельке вызывающего, и авторитет минта — программа CLMM. Программа привязывает владение позицией к **тому, кто держит баланс в ATA этого минта** во время CPI.

Следствия:

* **Позиции передаваются.** Кошелёк может продать или раздать позицию, передав NFT. Новый владелец может вызвать `CollectRewards`, `IncreaseLiquidity` и т. д.
* **Позиции доступны вне CLMM.** Маркетплейсы и кошельки отображают позиции как другие NFT. SDK устанавливает разумные `name`/`symbol` в метаданные минта.
* **PDA позиции выводится из минта NFT.** Вы можете найти `PersonalPositionState` без знания текущего владельца.

### Позиции Token-2022

Новые пулы CLMM могут создавать позиции в Token-2022 вместо классического SPL Token. Программа предоставляет две параллельные инструкции открытия — `OpenPosition` и `OpenPositionWithToken22Nft` — с идентичной семантикой, кроме того, какой программа токена владеет минтом NFT. Совместимость кошельков и маркетплейсов различается; интерфейс Raydium отслеживает оба варианта.

## Правила допустимого диапазона

При `OpenPosition` программа проверяет:

1. `tick_lower < tick_upper`.
2. `tick_lower % tick_spacing == 0` и `tick_upper % tick_spacing == 0`.
3. `MIN_TICK <= tick_lower` и `tick_upper <= MAX_TICK`.
4. Вызывающий предоставил массивы тиков, содержащие `tick_lower` и `tick_upper` — либо уже инициализированные, либо через `init_tick_array` в той же транзакции.
5. Аккаунт расширения битовой карты, если эта позиция выходит в диапазон расширения.

Если любая проверка не пройдена, инструкция откатывается с `InvalidTickIndex`, `NotApproved` или `InsufficientLiquidity` в зависимости от нарушенного ограничения. См. [`reference/error-codes`](/ru/reference/error-codes).

## "В диапазоне" против "вне диапазона"

Позиция находится **в диапазоне**, когда `tick_lower <= tick_current < tick_upper`. Только позиции в диапазоне способствуют `PoolState.liquidity` и, следовательно, только они получают комиссии за свопы.

Позиция вне диапазона:

* Держит 100% *одного* токена (того, мимо которого цена уже прошла). Конкретно, если `tick_current < tick_lower`, позиция держит только token1 (она уже была "продана" движением цены); если `tick_current >= tick_upper`, она держит только token0.
* **Не** получает комиссии за свопы.
* **Продолжает** получать вознаграждения, если потоки вознаграждения пула выделяют средства на ликвидность вне диапазона — но поведение Raydium по умолчанию "выделять только в диапазон", соответствуя соглашению Uniswap v3. См. [`products/clmm/fees`](/ru/products/clmm/fees).

LP, управляющие позициями CLMM, тратят большую часть внимания на поддержание позиций в диапазоне при движении цены.

## Типичные ошибки интеграции

* **Граничные точки не кратные интервалу.** Код, который вычисляет тик из целевой цены, должен округлить его к кратному `tick_spacing` перед передачей в `OpenPosition`. Вспомогательные функции SDK (`TickUtils.getTickWithPriceAndTickspacing`) это делают; самодельная математика часто не делает.
* **Отсутствующие массивы тиков.** Открытие широкой позиции может потребовать инициализации нескольких массивов тиков; забывчивость передать их как записываемые аккаунты откатывает инструкцию. SDK-метод `openPositionFromBase` возвращает список для вас.
* **Устаревший тик после свопа.** `tick_current` может пересечь много тиков в одном свопе. Если ваш интерфейс показывает "текущий тик" из одного вызова RPC, а потом открывает позицию в более позднем, относительное положение относительно активной цены может отличаться на десятки тиков. Переполучите данные перед подписанием.
* **NFT позиций с дополнительными метаданными.** Если вы создаёте кошелёк, распознающий позиции Raydium, определяйте их по авторитету минта (= PDA программы CLMM), а не по жёстко закодированному полю метаданных.

## Что дальше

* [Math](/ru/products/clmm/math) — пошаговое объяснение свопа и вывод прироста комиссии, в котором участвуют граница тиков.
* [Accounts](/ru/products/clmm/accounts) — макеты `TickArrayState` и `PositionState`.
* [Fees and rewards](/ru/products/clmm/fees) — как нахождение в диапазоне контролирует накопление комиссий.
* [`algorithms/clmm-math`](/ru/algorithms/clmm-math) — общий вывод формул концентрированной ликвидности.

Источники:

* [`raydium-io/raydium-clmm` — модули `tick_array`, `tick`, `position`](https://github.com/raydium-io/raydium-clmm)
* "Uniswap v3 Core" whitepaper, §6 (ticks), §7 (fee growth)
