> ## 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>
  **이 페이지는 AI 자동 번역입니다. 모든 내용은 영문판을 기준으로 합니다.**

  [영문판 보기 →](/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`을 강제합니다. 실제로 웹 UI는 사용자가 도달 불가능한 틱에 유동성을 잠그는 것을 방지하기 위해 범위를 훨씬 더 좁게 제한합니다.

## 틱 간격

풀의 `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` 인접 틱(현재 Raydium CLMM에서는 60)이 단일 `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, …`.

`tick_spacing = 60`에서 포지션 끝점 `t = 2040`은 `start_tick_index = 0`인 틱 배열에 있습니다. 포지션 끝점 `t = 4200`은 `start_tick_index = 3600`인 배열에 있습니다.

### 배열이 생성될 때

틱 배열은 지연 로딩됩니다: 내부의 틱을 참조하는 **첫 번째** 포지션이 배열을 초기화하고 렌트를 지불합니다. 스왑은 틱 배열을 초기화하지 않습니다. 비트맵을 사용하여 초기화되지 않은 배열을 건너뜁니다. SDK의 포지션 열기 플로우는 선택한 범위를 검사하고 건드리는 틱 배열 목록을 계산하며 누락된 배열이 있으면 `OpenPosition`과 같은 트랜잭션에 `init_tick_array` 명령어를 추가합니다.

### 틱 배열은 닫혀지지 않습니다

틱 배열이 초기화되면 풀의 수명 동안 유지됩니다. 프로그램은 `initialized_tick_count`가 0으로 돌아온 후에도 틱 배열을 닫을 방법을 제공하지 않습니다. 틱 배열에 대한 렌트 복구가 없습니다. 배열에 접근한 첫 번째 포지션이 지불한 렌트는 그 계정에 영구적으로 잠깁니다. 이는 의도적 트레이드오프입니다: 기존 틱 배열을 재사용하는 것은 모든 후속 포지션에 무료이므로, 거래량이 많은 풀은 변동이 많아도 `(pool, start_tick_index)` 슬롯당 한 번만 렌트 비용을 지불합니다.

### 비트맵

"현재 틱의 좌측/우측 다음 초기화된 틱 찾기"는 빨라야 합니다. 스왑이 많은 틱을 지날 수 있기 때문입니다. 풀은 틱 0 주변 ±1,024 배열 범위에 대해 `PoolState`에 인라인 1비트-틱-배열 비트맵을 저장합니다. 그 범위 밖(전체 범위 포지션, 특이한 설정)에서는 `TickArrayBitmapExtension`이 오버플로우를 제공합니다.

스왑은 비트맵을 따라갑니다: `lowest_set_bit_above(tick_current_array_index)`는 스왑이 향하는 방향의 초기화된 틱이 있는 다음 배열을 제공합니다. 그 배열 내에서 유사한 비트 스캔이 다음 초기화된 틱을 찾습니다.

## `liquidity_gross`와 `liquidity_net`

모든 **초기화된** 틱은 두 개의 유동성 값을 저장합니다:

* **`liquidity_gross`** — 이 틱을 끝점으로 참조하는 모든 포지션의 `L` 합. `liquidity_gross`가 0에 도달하면 틱은 초기화되지 않고 비트맵에서 제거될 수 있습니다.
* **`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 하한  | 100               | +100            |
| `-60`  | B 하한  | 50                | +50             |
| `0`    | A 상한  | 100               | −100            |
| `60`   | B 상한  | 50                | −50             |

다양한 `tick_current` 값에 대한 풀 수준 `liquidity`:

* `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 프로그램입니다. 프로그램은 포지션 소유권을 **CPI 시점에서 그 민트의 ATA에 잔액을 보유한 모든 사람**에게 키합니다.

결과:

* **포지션은 양도 가능합니다.** 지갑은 NFT를 이전하여 포지션을 판매하거나 에어드롭할 수 있습니다. 새 보유자는 `CollectRewards`, `IncreaseLiquidity` 등을 호출할 수 있습니다.
* **포지션은 CLMM 외부에서 주소 지정 가능합니다.** 마켓플레이스와 지갑은 다른 NFT처럼 포지션을 표시합니다. SDK는 민트 메타데이터에 합리적인 `name`/`symbol`을 설정합니다.
* **포지션의 PDA는 NFT 민트에서 파생됩니다.** 현재 보유자를 모르고도 `PersonalPositionState`를 찾을 수 있습니다.

### Token-2022 포지션

최신 CLMM 풀은 클래식 SPL Token 대신 Token-2022에서 포지션을 민팅할 수 있습니다. 프로그램은 두 개의 병렬 열기 명령어를 노출합니다. `OpenPosition`과 `OpenPositionWithToken22Nft`는 NFT 민트를 소유한 토큰 프로그램을 제외하고 동일한 의미를 가집니다. 지갑 및 마켓플레이스 호환성은 다릅니다. Raydium의 UI는 둘 다 추적합니다.

## 허용된 범위 규칙

`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`](/ko/reference/error-codes)를 참조하세요.

## "범위 내" vs "범위 외"

포지션은 `tick_lower <= tick_current < tick_upper`일 때 **범위 내**입니다. 범위 내 포지션만 `PoolState.liquidity`에 기여하므로 스왑 수수료를 얻습니다.

범위 외 포지션:

* 범위가 지나간 **한 가지** 토큰의 100%를 보유합니다. 구체적으로, `tick_current < tick_lower`이면 포지션은 토큰1만 보유합니다(가격이 멀어지면서 이미 "판매"되었음). `tick_current >= tick_upper`이면 토큰0만 보유합니다.
* 스왑 수수료를 **얻지 않습니다**.
* 풀의 보상 스트림이 범위 외 유동성에 방출하면 계속해서 보상을 적립합니다. 하지만 Raydium의 기본 동작은 "범위 내에만 방출"로, Uniswap v3 규칙과 일치합니다. [`products/clmm/fees`](/ko/products/clmm/fees)를 참조하세요.

CLMM 포지션을 관리하는 LP는 가격이 움직이면서 포지션을 범위 내로 유지하는 데 대부분의 관심을 집중합니다.

## 일반적인 통합 함정

* **간격을 벗어난 끝점.** 목표 가격에서 틱을 계산하는 코드는 `OpenPosition`에 전달하기 전에 `tick_spacing`의 배수로 스냅해야 합니다. SDK 헬퍼(`TickUtils.getTickWithPriceAndTickspacing`)는 이를 수행합니다. 직접 작성한 수학은 종종 그렇지 않습니다.
* **누락된 틱 배열.** 넓은 포지션을 열려면 여러 틱 배열을 초기화해야 할 수 있습니다. 쓰기 가능한 계정으로 전달하는 것을 잊으면 되돌려집니다. SDK의 `openPositionFromBase`는 목록을 반환합니다.
* **스왑 후 오래된 틱.** `tick_current`는 한 번의 스왑에서 많은 틱을 지날 수 있습니다. UX가 한 RPC 호출에서 "현재 틱"을 표시하고 나중에 포지션을 열면, 라이브 가격에 대한 상대 포지션이 수십 틱 떨어져 있을 수 있습니다. 서명하기 직전에 다시 가져오세요.
* **추가 메타데이터가 있는 포지션 NFT.** Raydium 포지션을 인식하는 지갑을 구축하면 하드코딩된 메타데이터 필드가 아닌 민트 권한(= CLMM 프로그램의 PDA)으로 감지하세요.

## 다음으로 이동

* [수학](/ko/products/clmm/math) — 틱 경계가 참여하는 스왑 단계 통과 및 수수료 성장 도출.
* [계정](/ko/products/clmm/accounts) — `TickArrayState`와 `PositionState` 레이아웃.
* [수수료와 보상](/ko/products/clmm/fees) — 범위 내 여부가 수수료 적립을 제어하는 방법.
* [`algorithms/clmm-math`](/ko/algorithms/clmm-math) — 집중 유동성 공식의 공유 도출.

출처:

* [`raydium-io/raydium-clmm` — `tick_array`, `tick`, `position` 모듈](https://github.com/raydium-io/raydium-clmm)
* "Uniswap v3 Core" 백서, §6 (틱), §7 (수수료 성장)
