Skip to main content

lp-analyst

Use when analyzing LP positions, calculating impermanent loss, optimizing position ranges, estimating fee revenue, rebalancing strategies, or building automated liquidity management. Covers both Uniswap V3 NonfungiblePositionManager and V4 PositionManager positions.

설치로 이동

소스 정보

저장소
ccashwell/evm-cortex
최근 소스 활동
2026년 4월 10일 16:31
감지된 SKILL.md 언어
영어
스타
131
포크
18

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
lp-analyst
description
Use when analyzing LP positions, calculating impermanent loss, optimizing position ranges, estimating fee revenue, rebalancing strategies, or building automated liquidity management. Covers both Uniswap V3 NonfungiblePositionManager and V4 PositionManager positions.
# LP Position Analysis ## Impermanent Loss Mathematics ### V2 Full-Range IL For a 50/50 constant-product pool where `r = current_price / entry_price`: ``` IL(r) = 2√r / (1 + r) - 1 ``` | Price Change | r | IL | |-------------|-----|---------| | -50% | 0.50 | -5.72% | | -25% | 0.75 | -1.03% | | -10% | 0.90 | -0.14% | | 0% | 1.00 | 0.00% | | +10% | 1.10 | -0.14% | | +25% | 1.25 | -0.62% | | +50% | 1.50 | -2.02% | | +100% | 2.00 | -5.72% | | +300% | 4.00 | -20.00% | IL is always non-positive. The loss is symmetric on a log scale: a 2x and a 0.5x move produce the same IL. ### V3 Concentrated Liquidity IL For a position with tick range `[tickLower, tickUpper]` mapping to price range `[Pa, Pb]` where `Pa < Pb`, with liquidity `L` and current price `P`: **Position token amounts (the core V3 math):** ``` If Pa ≤ P ≤ Pb (in range): amount0 = L × (1/√P - 1/√Pb) amount1 = L × (√P - √Pa) If P < Pa (below range — 100% token0): amount0 = L × (1/√Pa - 1/√Pb) amount1 = 0 If P > Pb (above range — 100% token1): amount0 = 0 amount1 = L × (√Pb - √Pa) ``` **Position value in token1 terms:** ``` value(P) = amount0 × P + amount1 ``` Expanding for the in-range case: ``` value(P) = L × (P/√P - P/√Pb + √P - √Pa) = L × (√P - √Pa + √P - P/√Pb) = L × (2√P - √Pa - P/√Pb) ``` **HODL value** (holding the initial token amounts without providing liquidity): At entry price `P₀` (in range), the initial amounts are: ``` a0 = L × (1/√P₀ - 1/√Pb) a1 = L × (√P₀ - √Pa) ``` HODL value at current price `P`: ``` value_HODL(P) = a0 × P + a1 = L × (P/√P₀ - P/√Pb + √P₀ - √Pa) ``` **Impermanent loss:** ``` IL = value_LP(P) / value_HODL(P) - 1 ``` Concentrated positions amplify IL relative to V2. The amplification factor equals the capital efficiency multiplier. ### Worked Example: ETH/USDC Setup: - Entry price: P₀ = 3000 USDC/ETH - Range: Pa = 2500, Pb = 3500 - Deposit: 1 ETH + 3000 USDC worth of value Compute initial amounts (P₀ = 3000, in range): ``` √3000 ≈ 54.772 √2500 = 50.000 √3500 ≈ 59.161 Liquidity L from 1 ETH + equivalent USDC: From token0: L = amount0 / (1/√P - 1/√Pb) From token1: L = amount1 / (√P - √Pa) Using the ratio to find L for a balanced deposit of value V at price P₀: V = L × (2√P₀ - √Pa - P₀/√Pb) V = L × (2 × 54.772 - 50.000 - 3000/59.161) V = L × (109.544 - 50.000 - 50.709) V = L × 8.835 For V = 6000 USDC (1 ETH at $3000 + 3000 USDC): L = 6000 / 8.835 ≈ 679.0 ``` Now ETH moves to $3500 (upper bound): ``` P = 3500 = Pb → position is 100% USDC (token1) amount0 = 0 amount1 = L × (√Pb - √Pa) = 679.0 × (59.161 - 50.000) = 679.0 × 9.161 ≈ 6220 value_LP = 6220 USDC ``` HODL value at $3500: ``` Initial amounts at P₀ = 3000: a0 = 679.0 × (1/54.772 - 1/59.161) = 679.0 × (0.01826 - 0.01690) = 679.0 × 0.001359 ≈ 0.923 ETH a1 = 679.0 × (54.772 - 50.000) = 679.0 × 4.772 ≈ 3240 USDC value_HODL = 0.923 × 3500 + 3240 = 3230 + 3240 = 6470 USDC ``` Impermanent loss: ``` IL = 6220 / 6470 - 1 ≈ -3.86% ``` Compare with V2 full-range IL at the same price move (r = 3500/3000 ≈ 1.167): ``` IL_v2 = 2√1.167 / (1 + 1.167) - 1 = 2 × 1.0801 / 2.167 - 1 ≈ -0.28% ``` The concentrated position suffers ~14x more IL — consistent with the capital efficiency multiplier for this range. ## Fee Revenue Estimation ### Fee Accrual Model ``` fee_revenue = volume_in_range × fee_tier × (position_liquidity / total_liquidity_in_range) ``` Annualized: ``` annual_fees = daily_fee_revenue × 365 fee_APR = annual_fees / position_value ``` The net return of an LP position is: ``` net_return = fee_APR + IL ``` A position is profitable when fee revenue exceeds IL. ### Fee Tiers | Tier | Fee | Tick Spacing | Typical Pairs | |------|-----|-------------|---------------| | 0.01% | 100 | 1 | Stablecoin/stablecoin (USDC/USDT) | | 0.05% | 500 | 10 | Correlated assets (wstETH/ETH) | | 0.30% | 3000 | 60 | Standard pairs (ETH/USDC) | | 1.00% | 10000 | 200 | Exotic / long-tail pairs | ### Active Time Ratio Concentrated positions only earn fees while the current price is within range. The active time ratio `α` represents the fraction of time the position is in range: ``` effective_fee_APR = fee_APR × α ``` For a ±10% range on ETH/USDC, historical α is typically 60-80% over a month. Narrower ranges have lower α. ### Fee Growth Tracking (V3) Uniswap V3 tracks cumulative fees per unit of liquidity using Q128.128 fixed-point accumulators: ``` feeGrowthGlobal0X128 — cumulative token0 fees per unit liquidity (pool-wide) feeGrowthGlobal1X128 — cumulative token1 fees per unit liquidity (pool-wide) ``` Per-position uncollected fees: ``` uncollected0 = (feeGrowthInside0CurrentX128 - feeGrowthInside0LastX128) × liquidity / 2^128 uncollected1 = (feeGrowthInside1CurrentX128 - feeGrowthInside1LastX128) × liquidity / 2^128 ``` Where `feeGrowthInsideX128` is computed from the tick-level `feeGrowthOutside` values: ```solidity // Pseudocode for feeGrowthInside if currentTick >= tickUpper: feeGrowthInside = feeGrowthOutside[tickUpper] - feeGrowthOutside[tickLower] elif currentTick < tickLower: feeGrowthInside = feeGrowthOutside[tickLower] - feeGrowthOutside[tickUpper] else: feeGrowthInside = feeGrowthGlobal - feeGrowthOutside[tickLower] - feeGrowthOutside[tickUpper] ``` ## Position Range Optimization ### Capital Efficiency Multiplier For a range `[Pa, Pb]`, capital efficiency relative to full range is: ``` efficiency = 1 / (1 - √(Pa / Pb)) ``` | Range | Pa/Pb | Efficiency | |-------|-------|-----------| | ±0.1% (stables) | 0.998 | ~1000x | | ±1% | 0.980 | ~100x | | ±5% | 0.905 | ~19x | | ±10% | 0.818 | ~5.2x | | ±25% | 0.600 | ~3.8x | | ±50% | 0.333 | ~2.2x | | Full range | 0→∞ | 1x | Higher efficiency means more fees earned per dollar of capital, but also more IL per dollar and more frequent out-of-range events. ### Range Selection Heuristics **Stablecoin pairs (USDC/USDT):** - Range: ±0.05% to ±0.5% around peg - Fee tier: 0.01% - Rebalance: rarely needed if peg holds - Capital efficiency: 200x–2000x **Correlated pairs (wstETH/ETH):** - Range: ±1% to ±5% - Fee tier: 0.05% - Rebalance: weekly or when staking rate changes materially - Capital efficiency: 20x–100x **Major pairs (ETH/USDC):** - Range: ±10% to ±30% based on volatility regime - Fee tier: 0.30% - Rebalance: when price approaches range boundary - Capital efficiency: 3x–10x **Volatile pairs (memecoins, new tokens):** - Range: ±50% or wider - Fee tier: 1.00% - Rebalance: avoid — gas often exceeds benefit - Capital efficiency: 1.5x–3x ### Tick Math Prices map to ticks via: ``` tick = floor(log(price) / log(1.0001)) price = 1.0001^tick ``` Tick spacing constrains which ticks can be used. A position's range must align to the pool's tick spacing: ``` tickLower = floor(desired_tick / tickSpacing) × tickSpacing tickUpper = ceil(desired_tick / tickSpacing) × tickSpacing ``` ## Reading Positions Onchain ### V3 NonfungiblePositionManager ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {INonfungiblePositionManager} from "@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol"; interface IPositionReader { function analyzePosition(uint256 tokenId) external view returns ( address token0, address token1, uint24 fee, int24 tickLower, int24 tickUpper, uint128 liquidity ); } contract V3PositionReader { INonfungiblePositionManager public immutable NPM; constructor(address npm_) { NPM = INonfungiblePositionManager(npm_); } function getPosition(uint256 tokenId) external view returns ( address token0, address token1, uint24 fee, int24 tickLower, int24 tickUpper, uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, uint128 tokensOwed0, uint128 tokensOwed1 ) { ( , // nonce , // operator token0, token1, fee, tickLower, tickUpper, liquidity, feeGrowthInside0LastX128, feeGrowthInside1LastX128, tokensOwed0, tokensOwed1 ) = NPM.positions(tokenId); } } ``` ### V4 PositionManager ```solidity import {IPositionManager} from "v4-periphery/src/interfaces/IPositionManager.sol"; import {PoolKey} from "v4-core/src/types/PoolKey.sol"; import {PositionInfo} from "v4-periphery/src/libraries/PositionInfoLibrary.sol"; contract V4PositionReader { IPositionManager public immutable PM; constructor(address pm_) { PM = IPositionManager(pm_); } /// @notice Reads a V4 LP position's pool key, tick range, and liquidity function getPosition(uint256 tokenId) external view returns ( PoolKey memory poolKey, int24 tickLower, int24 tickUpper, uint128 liquidity ) { PositionInfo info; (poolKey, info) = PM.getPoolAndPositionInfo(tokenId); tickLower = info.tickLower(); tickUpper = info.tickUpper(); liquidity = PM.getPositionLiquidity(tokenId); } } ``` ### Converting Ticks to Prices ```solidity import {TickMath} from "v4-core/src/libraries/TickMath.sol"; /// @notice Convert a tick to a human-readable price /// @dev price = 1.0001^tick, adjusted for token decimals function tickToPrice(int24 tick, uint8 decimals0, uint8 decimals1) pure returns (uint256) { uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(tick); // price = (sqrtPriceX96 / 2^96)^2 × 10^(decimals0 - decimals1) uint256 priceX192 = uint256(sqrtPriceX96) * uint256(sqrtPriceX96); return priceX192 * (10 ** decimals0) / (10 ** decimals1) >> 192; } ``` ## Position Value Calculation ### Token Amounts from Liquidity ```solidity import {LiquidityAmounts} from "v4-periphery/src/libraries/LiquidityAmounts.sol"; import {TickMath} from "v4-core/src/libraries/TickMath.sol"; /// @notice Calculate the token amounts for a position function getAmounts( int24 tickCurrent, int24 tickLower, int24 tickUpper, uint128 liquidity ) pure returns (uint256 amount0, uint256 amount1) { uint160 sqrtPriceCurrent = TickMath.getSqrtPriceAtTick(tickCurrent); uint160 sqrtPriceLower = TickMath.getSqrtPriceAtTick(tickLower); uint160 sqrtPriceUpper = TickMath.getSqrtPriceAtTick(tickUpper); (amount0, amount1) = LiquidityAmounts.getAmountsForLiquidity( sqrtPriceCurrent, sqrtPriceLower, sqrtPriceUpper, liquidity ); } ``` ### Value in USD Terms ``` value_usd = amount0 × price0_usd + amount1 × price1_usd ``` For ETH/USDC where token0 = USDC, token1 = WETH: ``` value_usd = amount0 × 1.0 + amount1 × eth_price_usd ``` Always check token ordering — V3/V4 enforce `token0 < token1` by address sort. ## Fee Collection ### V3 Fee Collection The NonfungiblePositionManager accumulates fees internally. To collect, first poke the position to update fee accounting, then call `collect`: ```solidity /// @notice Collect all accrued fees from a V3 position /// @dev A zero-liquidity decrease pokes the position to update fee snapshots function collectFees( INonfungiblePositionManager npm, uint256 tokenId ) external returns (uint256 collected0, uint256 collected1) { npm.decreaseLiquidity(INonfungiblePositionManager.DecreaseLiquidityParams({ tokenId: tokenId, liquidity: 0, amount0Min: 0, amount1Min: 0, deadline: block.timestamp })); (collected0, collected1) = npm.collect(INonfungiblePositionManager.CollectParams({ tokenId: tokenId, recipient: msg.sender, amount0Max: type(uint128).max, amount1Max: type(uint128).max })); } ``` ### V4 Fee Collection V4 positions collect fees through the `PositionManager.collect` action inside a multicall/modifyLiquidities batch. Fees settle through the PoolManager's transient accounting. ## Rebalancing Strategies ### Time-Based Rebalance at fixed intervals (e.g., every 24 hours, every 7 days). Simple to implement with Chainlink Automation or Gelato tasks. **Pros:** predictable gas spend, simple logic **Cons:** rebalances even when unnecessary, misses urgent rebalances when price moves fast
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기