
Lp Math
- 195 installs
- 257 repo stars
- Updated June 24, 2026
- agiprolabs/claude-trading-skills
lp-math is a Claude Code skill covering AMM liquidity-provision mathematics: constant-product, concentrated liquidity, price impact, and LP share calculations.
About
lp-math covers the mathematics of AMM liquidity provision, including constant-product (xy=k), concentrated liquidity, price impact, and LP share calculations. A developer uses it to estimate price impact before large trades, evaluate LP profitability, and compare capital efficiency across pool types. It provides formulas and worked examples for Solana DEXes.
- Covers AMM math: constant-product (xy=k), concentrated liquidity, price impact, LP shares
- Ships amm_calculator.py and clmm_calculator.py plus AMM-formula and pool-mechanics references
- Includes worked xy=k and CLMM capital-efficiency examples
Lp Math by the numbers
- 195 all-time installs (skills.sh)
- Ranked #102 of 479 Web3 & Blockchain skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
lp-math capabilities & compatibility
- Capabilities
- lp math · impermanent loss · liquidity analysis
- Use cases
- data analysis · research · trading
What lp-math says it does
Automated Market Makers (AMMs) replace traditional orderbooks with liquidity pools.
A ±5% range is ~20x more capital-efficient than full-range, but the position goes 100% into one asset if price moves outside the range.
npx skills add https://github.com/agiprolabs/claude-trading-skills --skill lp-mathAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 195 |
|---|---|
| repo stars | ★ 257 |
| Last updated | June 24, 2026 |
| Repository | agiprolabs/claude-trading-skills ↗ |
What it does
Compute AMM price impact, LP shares, and capital efficiency across pool types.
Who is it for?
Computing trade output, price impact, LP shares, and CLMM capital efficiency.
Skip if: Impermanent loss (use impermanent-loss) or executing swaps.
When should I use this skill?
You need AMM math to estimate price impact or evaluate LP economics.
By the numbers
- xy=k constant-product model
- ±5% range ≈20x capital efficiency example
- worked 5-SOL swap example
Files
LP Math — AMM Liquidity Provision Mathematics
Automated Market Makers (AMMs) replace traditional orderbooks with liquidity pools. Instead of matching buyers and sellers, a mathematical formula determines prices based on reserve ratios. Liquidity providers (LPs) deposit both assets into a pool and earn fees from every trade.
Understanding the math behind AMMs is essential for:
- Evaluating whether providing liquidity is profitable after impermanent loss
- Estimating price impact before executing large trades
- Comparing capital efficiency across pool types (constant product vs concentrated)
- Calculating expected fee revenue for a given pool position
Related skills: See impermanent-loss for IL calculations, yield-analysis for LP yield modeling, liquidity-analysis for pool depth assessment.
---
1. Constant Product AMM (xy = k)
The foundational AMM model used by Raydium V4 and most Solana DEXes.
Core Invariant
x * y = kWhere:
x= reserve amount of token X (e.g., SOL)y= reserve amount of token Y (e.g., USDC)k= constant product (increases over time from fees)
Spot Price
P = x / y (price of Y in terms of X)
P = y / x (price of X in terms of Y)For a pool with 100 SOL and 10,000 USDC: price of SOL = 10,000 / 100 = 100 USDC.
Trade Execution
When a trader swaps Δx of token X into the pool:
# Output amount (before fees)
delta_y = y * delta_x / (x + delta_x)
# With fee (e.g., 0.3%)
delta_y_after_fee = delta_y * (1 - fee_rate)
# New reserves
x_new = x + delta_x
y_new = y - delta_y_after_feeThe key insight: larger trades get worse prices because each unit moves the ratio further.
Inverse Calculation
To get a specific output amount Δy, the required input is:
delta_x = x * delta_y / (y - delta_y)Price After Trade
price_new = y_new / x_newWorked Example
Pool: 100 SOL / 10,000 USDC (k = 1,000,000), fee = 0.3%
Buy 5 SOL worth of USDC: 1. Gross output: 10,000 * 5 / (100 + 5) = 476.19 USDC 2. Fee: 476.19 * 0.003 = 1.43 USDC 3. Net output: 474.76 USDC 4. Effective price: 474.76 / 5 = 94.95 USDC/SOL (vs spot 100) 5. Price impact: (100 - 94.95) / 100 = 5.05% 6. New reserves: 105 SOL / 9,525.24 USDC 7. New k: 105 * 9,525.24 = 1,000,150.2 (k increased from fees)
See references/amm_formulas.md for complete derivations.
---
2. Concentrated Liquidity (CLMM)
Used by Orca Whirlpool, Raydium CLMM, and Meteora DLMM. Liquidity is only active within a chosen price range [P_lower, P_upper].
Key Concepts
L = sqrt(x * y) # Liquidity within the active range
price_at_tick = 1.0001^tick # Tick-to-price conversionCapital Efficiency
Concentrating liquidity in a narrow range provides more depth per dollar:
# Capital efficiency ratio
efficiency = sqrt(P_upper / P_lower) / (sqrt(P_upper / P_lower) - 1)
# Example: ±5% range around $100 SOL
P_lower, P_upper = 95, 105
efficiency = sqrt(105/95) / (sqrt(105/95) - 1) # ≈ 20.5xA ±5% range is ~20x more capital-efficient than full-range, but the position goes 100% into one asset if price moves outside the range.
Position Value
For a CLMM position with liquidity L in range [P_lower, P_upper] at current price P:
if P <= P_lower:
# All in token X (below range)
value_x = L * (1/sqrt(P_lower) - 1/sqrt(P_upper))
value_y = 0
elif P >= P_upper:
# All in token Y (above range)
value_x = 0
value_y = L * (sqrt(P_upper) - sqrt(P_lower))
else:
# In range — holds both tokens
value_x = L * (1/sqrt(P) - 1/sqrt(P_upper))
value_y = L * (sqrt(P) - sqrt(P_lower))Range Strategy Comparison
| Range | Efficiency | IL Risk | Fee Capture | Best For |
|---|---|---|---|---|
| ±2% | ~50x | Very high | High if in range | Stablecoins, tight pegs |
| ±5% | ~20x | High | Good for trending | Active management |
| ±25% | ~4x | Moderate | Consistent | Semi-passive |
| ±100% | ~2x | Low | Lower per $ | Passive, volatile pairs |
| Full range | 1x | Baseline | Always earning | Set and forget |
See references/amm_formulas.md for full CLMM derivations.
---
3. Price Impact
Constant Product Impact
# Price impact as a fraction
price_impact = delta_x / (x + delta_x)
# As percentage of pool
pool_fraction = trade_value / pool_tvl
# Rule of thumb: impact ≈ 2 * pool_fraction for constant productMulti-Hop Impact
For a route through multiple pools, compound the impacts:
def multi_hop_impact(hops: list[dict]) -> float:
"""Calculate total price impact across route legs.
Args:
hops: List of {reserve_in, trade_amount} for each leg.
Returns:
Total price impact as a fraction.
"""
remaining = 1.0
for hop in hops:
leg_impact = hop["trade_amount"] / (hop["reserve_in"] + hop["trade_amount"])
remaining *= (1 - leg_impact)
return 1 - remainingImpact Thresholds
| Impact | Assessment | Action |
|---|---|---|
| < 0.1% | Negligible | Proceed normally |
| 0.1–0.5% | Low | Acceptable for most trades |
| 0.5–2% | Moderate | Consider splitting across pools |
| 2–5% | High | Split trade, use TWAP |
| > 5% | Severe | Reduce size or find deeper pools |
---
4. LP Share Calculations
Initial Deposit (Empty Pool)
shares = sqrt(x_deposited * y_deposited)The first depositor sets the ratio and receives shares equal to the geometric mean.
Subsequent Deposits
shares_minted = min(
x_added / x_reserve,
y_added / y_reserve
) * total_sharesDeposits must be proportional to the current reserve ratio. Any excess of one token is not used (or returned, depending on implementation).
Withdrawal
x_out = (shares_burned / total_shares) * x_reserve
y_out = (shares_burned / total_shares) * y_reserveYou always receive both tokens in the current ratio.
Share Value
share_value = pool_tvl / total_shares
your_value = your_shares * share_value---
5. Fee Accrual
Fees accumulate inside the pool, increasing k:
# Before trade: k = x * y
# After trade with fee:
# k_new = (x + delta_x) * (y - delta_y_net) > k
# The difference is the fee retained in the pool
# Fee APR estimation
daily_volume = 500_000 # USD
fee_rate = 0.003 # 0.3%
daily_fees = daily_volume * fee_rate # $1,500
tvl = 2_000_000 # $2M pool
fee_apr = (daily_fees * 365) / tvl # 27.4%For CLMM positions, fee earnings depend on:
- Whether price stays within your range (out-of-range = no fees)
- Your share of active liquidity in that range
- Total volume routed through the pool
# CLMM fee estimation
your_liquidity = 50_000 # Your L
total_liquidity = 1_000_000 # Total L in your tick range
your_share = your_liquidity / total_liquidity # 5%
your_daily_fees = daily_fees * your_share # $75---
6. Solana Pool Types
Raydium V4 (Constant Product)
- Model: Standard xy = k
- Fee: 0.25% (0.22% to LP, 0.03% to RAY buyback)
- Best for: New token launches, volatile pairs
- Note: Integrated with OpenBook for limit order flow
Orca Whirlpool (Concentrated Liquidity)
- Model: Concentrated liquidity with tick spacing
- Fee tiers: 0.01%, 0.05%, 0.3%, 1%
- Position: Represented as NFT (each position is unique)
- Best for: Major pairs (SOL/USDC), stablecoin pairs
Raydium CLMM
- Model: Concentrated liquidity (similar to Uniswap V3)
- Tick spacing: 1, 10, 60, 200
- Fee tiers: 0.01%, 0.05%, 0.25%, 1%
- Best for: Pairs with predictable ranges
Meteora DLMM (Dynamic Liquidity Market Maker)
- Model: Discrete bins instead of continuous ticks
- Strategies: Spot (uniform), Curve (concentrated), Bid-Ask (around current price)
- Fees: Dynamic, adjusting based on volatility
- Best for: Active LPs who rebalance frequently
See references/pool_mechanics.md for detailed mechanics and comparison.
---
7. Practical Decision Framework
Should You LP?
1. Calculate expected fee APR
2. Estimate impermanent loss for expected price movement
3. Net return = fee APR - IL
4. Compare to simply holding the assetsWhich Pool Type?
Stablecoin pair → CLMM with tight range (±0.5%)
Major pair (SOL/USDC) → CLMM with moderate range (±10-25%)
New/volatile token → Constant product (full range)
Active management → Meteora DLMM with dynamic rebalancingPosition Sizing for LP
# Never LP more than you can afford to lose to IL
max_lp_allocation = portfolio_value * 0.20 # 20% max in any single pool
# For volatile pairs, reduce further
volatility_adjustment = 1 - (annualized_vol / 2) # Scale down for vol
adjusted_allocation = max_lp_allocation * max(0.1, volatility_adjustment)---
Files
References
references/amm_formulas.md— Complete mathematical derivations for constant product and concentrated liquidity AMMsreferences/pool_mechanics.md— Solana-specific pool mechanics for Raydium, Orca, and Meteora
Scripts
scripts/amm_calculator.py— Constant product AMM calculator with trade simulation, LP shares, and fee accrualscripts/clmm_calculator.py— Concentrated liquidity calculator with position valuation, capital efficiency, and range comparison
---
Quick Reference
| Formula | Expression |
|---|---|
| Constant product | x * y = k |
| Spot price | P = y / x |
| Trade output | Δy = y * Δx / (x + Δx) |
| Required input | Δx = x * Δy / (y - Δy) |
| Price impact | Δx / (x + Δx) |
| Initial LP shares | sqrt(x * y) |
| Subsequent shares | min(Δx/x, Δy/y) * total |
| Fee APR | (daily_fees * 365) / TVL |
| CLMM efficiency | sqrt(P_u/P_l) / (sqrt(P_u/P_l) - 1) |
| Tick to price | 1.0001^tick |
AMM Formulas — Complete Derivations
Constant Product AMM (xy = k)
Price from Reserves
For a pool with reserves x (token X) and y (token Y):
Spot price of X in terms of Y: P_x = y / x
Spot price of Y in terms of X: P_y = x / yThe price is simply the ratio of reserves. This is the marginal price — the price for an infinitesimally small trade.
Trade Execution: Selling Token X for Token Y
A trader deposits Δx of token X and receives Δy of token Y.
The invariant must hold after the trade (ignoring fees):
(x + Δx) * (y - Δy) = k = x * ySolving for Δy:
y - Δy = x * y / (x + Δx)
Δy = y - x * y / (x + Δx)
Δy = y * (1 - x / (x + Δx))
Δy = y * Δx / (x + Δx)Key result: Δy = y * Δx / (x + Δx)
Inverse: Required Input for Desired Output
If you want exactly Δy of token Y, how much token X do you need?
(x + Δx) * (y - Δy) = x * y
x + Δx = x * y / (y - Δy)
Δx = x * y / (y - Δy) - x
Δx = x * (y / (y - Δy) - 1)
Δx = x * Δy / (y - Δy)Key result: Δx = x * Δy / (y - Δy)
Note: This diverges as Δy → y — you can never drain a pool completely.
With Fees
Most AMMs charge a fee f (e.g., 0.003 for 0.3%). The fee is taken from the input:
effective_input = Δx * (1 - f)
Δy = y * effective_input / (x + effective_input)After the trade, the new reserves are:
x_new = x + Δx # Full input added (fee stays in pool)
y_new = y - Δy # Output removed
k_new = x_new * y_new > k # k increases because fee staysThe fee causes k to grow monotonically, which is how LPs earn returns.
Price Impact
Effective execution price vs spot price:
spot_price = y / x
execution_price = Δy / Δx
price_impact = 1 - (execution_price / spot_price)
= 1 - (y * x) / ((x + Δx) * Δx * (y/x)) # simplifies to:
= Δx / (x + Δx)Key result: Price impact = Δx / (x + Δx)
For small trades where Δx << x: impact ≈ Δx / x
Worked Example
Pool: 100 SOL / 10,000 USDC. Fee: 0.3%.
Trade: Sell 5 SOL into the pool.
x = 100 SOL, y = 10,000 USDC, k = 1,000,000
Δx = 5 SOL, fee = 0.003
Step 1: Effective input
effective = 5 * (1 - 0.003) = 4.985 SOL
Step 2: Output
Δy = 10,000 * 4.985 / (100 + 4.985) = 474.76 USDC
Step 3: New reserves
x_new = 100 + 5 = 105 SOL
y_new = 10,000 - 474.76 = 9,525.24 USDC
k_new = 105 * 9,525.24 = 1,000,150.2 (> 1,000,000)
Step 4: Prices
Spot before: 10,000 / 100 = 100 USDC/SOL
Execution: 474.76 / 5 = 94.95 USDC/SOL
Spot after: 9,525.24 / 105 = 90.72 USDC/SOL
Impact: 5 / (100 + 5) = 4.76%
Step 5: Fees earned by LPs
Fee in USDC terms: 474.76 * 0.003 / (1 - 0.003) ≈ 1.43 USDC---
Concentrated Liquidity (CLMM)
Virtual Reserves
In a CLMM, liquidity L is concentrated in range [P_lower, P_upper]. The relationship to virtual reserves:
L = sqrt(x_virtual * y_virtual)
L² = x_virtual * y_virtualWhere virtual reserves represent what the pool "acts like" within the range.
Real vs Virtual Reserves
Real token amounts for liquidity L in range [P_a, P_b] at current price P:
x_real = L * (1/sqrt(P) - 1/sqrt(P_b)) when P_a ≤ P ≤ P_b
y_real = L * (sqrt(P) - sqrt(P_a)) when P_a ≤ P ≤ P_bOutside the range:
If P < P_a: x_real = L * (1/sqrt(P_a) - 1/sqrt(P_b)), y_real = 0
If P > P_b: x_real = 0, y_real = L * (sqrt(P_b) - sqrt(P_a))Position Value
Total value in terms of token Y (e.g., USDC):
If P ≤ P_a:
V = L * (1/sqrt(P_a) - 1/sqrt(P_b)) * P # All token X, valued at P
If P_a < P < P_b:
V = L * (sqrt(P) - sqrt(P_a)) # Y component
+ L * (1/sqrt(P) - 1/sqrt(P_b)) * P # X component valued at P
V = L * (2*sqrt(P) - sqrt(P_a) - P/sqrt(P_b))
If P ≥ P_b:
V = L * (sqrt(P_b) - sqrt(P_a)) # All token YLiquidity from Deposit
Given a deposit of Δx and Δy in range [P_a, P_b] at price P:
L_from_x = Δx / (1/sqrt(P) - 1/sqrt(P_b))
L_from_y = Δy / (sqrt(P) - sqrt(P_a))
L = min(L_from_x, L_from_y)The minimum ensures the deposit is balanced for the current price.
Capital Efficiency
A CLMM position in range [P_a, P_b] provides the same depth as a full-range position with:
efficiency = sqrt(P_b / P_a) / (sqrt(P_b / P_a) - 1)Examples:
±1% range: P_b/P_a = 1.02/0.98 = 1.0408 → efficiency ≈ 50x
±5% range: P_b/P_a = 1.05/0.95 = 1.1053 → efficiency ≈ 20x
±10% range: P_b/P_a = 1.10/0.90 = 1.2222 → efficiency ≈ 10x
±25% range: P_b/P_a = 1.25/0.75 = 1.6667 → efficiency ≈ 4x
±50% range: P_b/P_a = 1.50/0.50 = 3.0000 → efficiency ≈ 2.4xTick Math
Prices are discretized into ticks:
price_at_tick = 1.0001^tick
tick_at_price = log(price) / log(1.0001)Tick spacing determines the minimum granularity of price ranges:
- Spacing 1: every tick (~0.01% price increment)
- Spacing 10: every 10 ticks (~0.1% price increment)
- Spacing 60: every 60 ticks (~0.6% price increment)
---
LP Token Math
Minting (Initial Deposit)
The first depositor defines the initial price and receives:
shares = sqrt(Δx * Δy) - MINIMUM_LIQUIDITYMINIMUM_LIQUIDITY (typically 1000 units) is permanently locked to prevent manipulation.
Minting (Subsequent Deposits)
shares = min(Δx / x_reserve, Δy / y_reserve) * total_supplyIf the depositor provides tokens in a different ratio than the pool, only the proportional amount is used. The excess should be returned or the transaction reverts.
Burning (Withdrawal)
x_out = (shares_burned / total_supply) * x_reserve
y_out = (shares_burned / total_supply) * y_reserveThis always returns both tokens in the current pool ratio.
No-Arbitrage Property
LP share value tracks the pool value. If the pool price deviates from the market price, arbitrageurs trade until they converge. This means:
share_price = (x_reserve * P_x + y_reserve * P_y) / total_supplyThe share price changes when: 1. Trades move the reserves (causes impermanent loss) 2. Fees accumulate (increases share value) 3. External rewards are added (if applicable)
Net LP return = Fee APR - Impermanent Loss + External Rewards
Solana Pool Mechanics
Raydium V4 (Constant Product)
Overview
Standard xy = k AMM, the most common pool type on Solana for new token launches.
Fee Structure
- Total fee: 0.25% per swap
- LP share: 0.22% (88% of fees go to LPs)
- Protocol: 0.03% (12% used for RAY buyback)
OpenBook Integration
Raydium V4 pools are linked to an OpenBook (formerly Serum) market. This means:
- Limit orders from OpenBook can fill against the AMM liquidity
- Additional volume flows through the pool from orderbook traders
- Pool creation requires creating an OpenBook market first
Pool Creation
1. Create an OpenBook market for the token pair 2. Initialize the Raydium AMM pool with initial liquidity 3. Set the initial price via the token ratio deposited 4. LP tokens are minted as SPL tokens
Characteristics
- Always active across all prices (full range)
- Simple to understand and manage
- Higher impermanent loss for volatile pairs
- No position management needed after deposit
- Suitable for long-tail tokens with unpredictable price movement
---
Orca Whirlpool (Concentrated Liquidity)
Overview
Concentrated liquidity AMM similar to Uniswap V3. LPs choose a price range for their liquidity.
Fee Tiers
| Tier | Fee | Tick Spacing | Best For |
|---|---|---|---|
| 0.01% | 1 bp | 1 | Stablecoin pairs (USDC/USDT) |
| 0.05% | 5 bp | 8 | Correlated pairs |
| 0.30% | 30 bp | 64 | Standard pairs (SOL/USDC) |
| 1.00% | 100 bp | 128 | Exotic/volatile pairs |
Position Representation
Each LP position is an NFT. This means:
- Positions are non-fungible (each has unique range)
- Can transfer or sell positions
- Multiple positions in the same pool at different ranges
- Position tracks uncollected fees separately
Tick Spacing and Precision
- Tick spacing determines minimum range granularity
- Lower spacing = finer control but more gas for swaps crossing many ticks
- Price at tick i:
P = 1.0001^i - Ticks must be multiples of the tick spacing
Fee Collection
Fees are not auto-compounded. LPs must: 1. Collect fees manually (transaction required) 2. Optionally re-deposit collected fees as new liquidity 3. Fees accrue in the tokens of the pool (not LP tokens)
Characteristics
- Capital efficient for predictable ranges
- Requires active management for optimal returns
- Position goes inactive if price moves outside range
- Higher IL risk for narrow ranges
- Fee income concentrated among in-range LPs
---
Raydium CLMM
Overview
Raydium's concentrated liquidity implementation, similar to Orca Whirlpool.
Fee Tiers and Tick Spacing
| Tier | Fee | Tick Spacing | Use Case |
|---|---|---|---|
| 0.01% | 1 bp | 1 | Stablecoins |
| 0.05% | 5 bp | 10 | Correlated assets |
| 0.25% | 25 bp | 60 | Major pairs |
| 1.00% | 100 bp | 200 | Volatile pairs |
Differences from Orca Whirlpool
- Different tick spacing values (10 vs 8, 60 vs 64, etc.)
- Integrated with Raydium's broader ecosystem
- Different fee distribution mechanics
- Protocol fee taken from LP fees
Position Management
- Positions are represented as on-chain accounts
- Can open multiple positions in the same pool
- Fees tracked per-position and collected manually
- Position can be closed at any time, returning tokens + uncollected fees
---
Meteora DLMM (Dynamic Liquidity Market Maker)
Overview
Uses discrete price bins instead of continuous ticks. Each bin holds liquidity at a single price point.
Bin System
- Price space divided into discrete bins
- Each bin represents a fixed price:
price = (1 + bin_step)^bin_id - Bin step determines price granularity (e.g., 0.1% between bins)
- Active bin: the bin containing the current price
Liquidity Distribution Strategies
| Strategy | Distribution | Best For |
|---|---|---|
| Spot | Uniform across bins | General purpose, passive LPs |
| Curve | Concentrated around current price | Active LPs expecting range-bound |
| Bid-Ask | Split around current price | Market makers |
Dynamic Fees
Meteora adjusts fees based on market conditions:
- Base fee: Set at pool creation
- Variable fee: Increases with volatility
- Total fee = base_fee + variable_fee
- Variable component uses an exponential moving average of recent volatility
Single-Sided Deposits
Unlike constant product pools, DLMM allows:
- Depositing only one token (into bins on one side of the current price)
- Useful for building a position gradually
- Deposit only token X in bins above current price (limit sell behavior)
- Deposit only token Y in bins below current price (limit buy behavior)
Characteristics
- Most flexible liquidity distribution
- Dynamic fees protect LPs during volatile periods
- Single-sided deposits enable limit-order-like strategies
- Bins make position management more intuitive than ticks
- Rebalancing is straightforward: remove from old bins, add to new bins
---
Comparison Table
| Feature | Raydium V4 | Orca Whirlpool | Raydium CLMM | Meteora DLMM |
|---|---|---|---|---|
| Model | xy = k | Concentrated | Concentrated | Discrete bins |
| Capital efficiency | 1x | Up to 4000x | Up to 4000x | Up to 4000x |
| Fee tiers | 0.25% fixed | 4 tiers | 4 tiers | Dynamic |
| Position type | Fungible LP token | NFT | Account | Account |
| Active management | Not needed | Recommended | Recommended | Recommended |
| Range selection | Full range only | Custom range | Custom range | Custom bins |
| Single-sided deposit | No | No | No | Yes |
| Fee auto-compound | Yes (in k) | No (manual) | No (manual) | No (manual) |
| Best for | New tokens | Major pairs | Major pairs | Active LPs |
| Complexity | Low | Medium | Medium | Medium-High |
---
Pool Selection Guide
By Asset Type
- Stablecoin pairs: CLMM with tight range (±0.5%), lowest fee tier
- Major pairs (SOL/USDC): CLMM with ±10-25% range, 0.3% fee tier
- New token launches: Raydium V4 constant product
- Volatile meme tokens: Raydium V4 or DLMM with wide distribution
- Actively managed: Meteora DLMM with frequent rebalancing
By LP Style
- Passive: Raydium V4 (set and forget, no range to manage)
- Semi-active: CLMM with wide range, rebalance weekly
- Active: Meteora DLMM, rebalance daily based on volatility
- Professional: Multiple narrow CLMM positions, algorithmic rebalancing
#!/usr/bin/env python3
"""Constant Product AMM Calculator.
Simulates a constant product (xy=k) automated market maker with trade execution,
LP share calculations, fee accrual tracking, and multi-trade simulation.
Usage:
python scripts/amm_calculator.py
python scripts/amm_calculator.py --demo
Dependencies:
None (pure math, standard library only)
Environment Variables:
None required
"""
import argparse
import math
import sys
from dataclasses import dataclass, field
from typing import Optional
# ── Data Models ─────────────────────────────────────────────────────
@dataclass
class TradeResult:
"""Result of executing a trade against the AMM."""
input_token: str
output_token: str
input_amount: float
output_amount: float
fee_amount: float
spot_price_before: float
spot_price_after: float
execution_price: float
price_impact_pct: float
k_before: float
k_after: float
@dataclass
class LPPosition:
"""An LP's position in the pool."""
address: str
shares: float
deposit_x: float
deposit_y: float
@dataclass
class ConstantProductPool:
"""A constant product (xy=k) AMM pool."""
token_x: str
token_y: str
reserve_x: float
reserve_y: float
fee_rate: float # e.g., 0.003 for 0.3%
lp_fee_share: float # fraction of fee going to LPs, e.g., 0.88
total_shares: float = 0.0
total_fees_x: float = 0.0
total_fees_y: float = 0.0
trade_count: int = 0
positions: list = field(default_factory=list)
@property
def k(self) -> float:
"""Current invariant value."""
return self.reserve_x * self.reserve_y
@property
def spot_price(self) -> float:
"""Price of token X in terms of token Y."""
if self.reserve_x == 0:
return 0.0
return self.reserve_y / self.reserve_x
@property
def tvl(self) -> float:
"""Total value locked (in terms of token Y, assuming spot price)."""
return self.reserve_y * 2 # Both sides equal value at equilibrium
def swap_x_for_y(self, delta_x: float) -> TradeResult:
"""Swap token X into the pool for token Y.
Args:
delta_x: Amount of token X to sell.
Returns:
TradeResult with execution details.
Raises:
ValueError: If delta_x is non-positive or output exceeds reserves.
"""
if delta_x <= 0:
raise ValueError(f"Trade amount must be positive, got {delta_x}")
spot_before = self.spot_price
k_before = self.k
# Apply fee to input
fee_x = delta_x * self.fee_rate
effective_input = delta_x - fee_x
# Calculate output
delta_y = self.reserve_y * effective_input / (self.reserve_x + effective_input)
if delta_y >= self.reserve_y:
raise ValueError(
f"Output {delta_y:.4f} {self.token_y} exceeds reserves "
f"{self.reserve_y:.4f} {self.token_y}"
)
# Update reserves
self.reserve_x += delta_x # Full input (fee stays in pool as X)
self.reserve_y -= delta_y
# Track fees and trades
self.total_fees_x += fee_x
self.trade_count += 1
spot_after = self.spot_price
execution_price = delta_y / delta_x if delta_x > 0 else 0
price_impact = 1 - (execution_price / spot_before) if spot_before > 0 else 0
return TradeResult(
input_token=self.token_x,
output_token=self.token_y,
input_amount=delta_x,
output_amount=delta_y,
fee_amount=fee_x,
spot_price_before=spot_before,
spot_price_after=spot_after,
execution_price=execution_price,
price_impact_pct=price_impact * 100,
k_before=k_before,
k_after=self.k,
)
def swap_y_for_x(self, delta_y: float) -> TradeResult:
"""Swap token Y into the pool for token X.
Args:
delta_y: Amount of token Y to sell.
Returns:
TradeResult with execution details.
Raises:
ValueError: If delta_y is non-positive or output exceeds reserves.
"""
if delta_y <= 0:
raise ValueError(f"Trade amount must be positive, got {delta_y}")
spot_before = self.spot_price
k_before = self.k
fee_y = delta_y * self.fee_rate
effective_input = delta_y - fee_y
delta_x = self.reserve_x * effective_input / (self.reserve_y + effective_input)
if delta_x >= self.reserve_x:
raise ValueError(
f"Output {delta_x:.4f} {self.token_x} exceeds reserves "
f"{self.reserve_x:.4f} {self.token_x}"
)
self.reserve_y += delta_y
self.reserve_x -= delta_x
self.total_fees_y += fee_y
self.trade_count += 1
spot_after = self.spot_price
# Price in terms of Y per X; buying X means inverse
execution_price_y_per_x = delta_y / delta_x if delta_x > 0 else 0
price_impact = (execution_price_y_per_x / spot_before - 1) if spot_before > 0 else 0
return TradeResult(
input_token=self.token_y,
output_token=self.token_x,
input_amount=delta_y,
output_amount=delta_x,
fee_amount=fee_y,
spot_price_before=spot_before,
spot_price_after=spot_after,
execution_price=execution_price_y_per_x,
price_impact_pct=price_impact * 100,
k_before=k_before,
k_after=self.k,
)
def calculate_required_input_x(self, desired_y: float) -> float:
"""Calculate token X needed to receive a specific amount of token Y.
Args:
desired_y: Desired output of token Y.
Returns:
Required input of token X (before fees).
Raises:
ValueError: If desired_y exceeds or equals reserves.
"""
if desired_y >= self.reserve_y:
raise ValueError(
f"Desired output {desired_y} >= reserves {self.reserve_y}"
)
# delta_x_effective = reserve_x * desired_y / (reserve_y - desired_y)
effective = self.reserve_x * desired_y / (self.reserve_y - desired_y)
# Account for fee: effective = delta_x * (1 - fee), so delta_x = effective / (1 - fee)
return effective / (1 - self.fee_rate)
def add_liquidity(
self, address: str, amount_x: float, amount_y: float
) -> LPPosition:
"""Add liquidity to the pool.
For the first deposit, shares = sqrt(x * y).
For subsequent deposits, shares are proportional to the smaller ratio.
Args:
address: Identifier for the LP.
amount_x: Amount of token X to deposit.
amount_y: Amount of token Y to deposit.
Returns:
LPPosition with share details.
Raises:
ValueError: If amounts are non-positive.
"""
if amount_x <= 0 or amount_y <= 0:
raise ValueError("Deposit amounts must be positive")
if self.total_shares == 0:
# Initial deposit
shares = math.sqrt(amount_x * amount_y)
self.reserve_x = amount_x
self.reserve_y = amount_y
else:
# Proportional deposit
ratio_x = amount_x / self.reserve_x
ratio_y = amount_y / self.reserve_y
share_ratio = min(ratio_x, ratio_y)
shares = share_ratio * self.total_shares
# Only use proportional amounts
used_x = share_ratio * self.reserve_x
used_y = share_ratio * self.reserve_y
self.reserve_x += used_x
self.reserve_y += used_y
# Adjust amounts to what was actually used
amount_x = used_x
amount_y = used_y
self.total_shares += shares
position = LPPosition(
address=address,
shares=shares,
deposit_x=amount_x,
deposit_y=amount_y,
)
self.positions.append(position)
return position
def remove_liquidity(self, shares: float) -> tuple[float, float]:
"""Remove liquidity by burning shares.
Args:
shares: Number of LP shares to burn.
Returns:
Tuple of (token_x_out, token_y_out).
Raises:
ValueError: If shares exceed total supply.
"""
if shares > self.total_shares:
raise ValueError(
f"Cannot burn {shares} shares, only {self.total_shares} exist"
)
if shares <= 0:
raise ValueError("Shares must be positive")
fraction = shares / self.total_shares
x_out = fraction * self.reserve_x
y_out = fraction * self.reserve_y
self.reserve_x -= x_out
self.reserve_y -= y_out
self.total_shares -= shares
return x_out, y_out
def share_value(self, shares: float) -> tuple[float, float]:
"""Calculate the current value of LP shares.
Args:
shares: Number of shares to value.
Returns:
Tuple of (token_x_value, token_y_value).
"""
if self.total_shares == 0:
return 0.0, 0.0
fraction = shares / self.total_shares
return fraction * self.reserve_x, fraction * self.reserve_y
def fee_apr_estimate(self, daily_volume: float) -> float:
"""Estimate annualized fee APR.
Args:
daily_volume: Expected daily trading volume (in token Y terms).
Returns:
Estimated APR as a decimal (e.g., 0.274 for 27.4%).
"""
daily_fees = daily_volume * self.fee_rate * self.lp_fee_share
if self.tvl == 0:
return 0.0
return (daily_fees * 365) / self.tvl
# ── Display Functions ───────────────────────────────────────────────
def print_pool_state(pool: ConstantProductPool) -> None:
"""Print the current state of the pool."""
print(f"\n{'─' * 60}")
print(f" Pool: {pool.token_x}/{pool.token_y}")
print(f" Reserves: {pool.reserve_x:,.4f} {pool.token_x} / "
f"{pool.reserve_y:,.4f} {pool.token_y}")
print(f" Spot Price: 1 {pool.token_x} = {pool.spot_price:,.4f} {pool.token_y}")
print(f" k = {pool.k:,.2f}")
print(f" TVL ≈ {pool.tvl:,.2f} {pool.token_y}")
print(f" Fee: {pool.fee_rate * 100:.2f}%")
print(f" Total Trades: {pool.trade_count}")
print(f" Total Fees: {pool.total_fees_x:,.4f} {pool.token_x} / "
f"{pool.total_fees_y:,.4f} {pool.token_y}")
print(f" LP Shares: {pool.total_shares:,.4f}")
print(f"{'─' * 60}")
def print_trade_result(result: TradeResult) -> None:
"""Print trade execution details."""
print(f"\n Trade: {result.input_amount:,.4f} {result.input_token} → "
f"{result.output_amount:,.4f} {result.output_token}")
print(f" Fee paid: {result.fee_amount:,.6f} {result.input_token}")
print(f" Spot price before: {result.spot_price_before:,.4f}")
print(f" Execution price: {result.execution_price:,.4f}")
print(f" Spot price after: {result.spot_price_after:,.4f}")
print(f" Price impact: {result.price_impact_pct:,.4f}%")
print(f" k change: {result.k_before:,.2f} → {result.k_after:,.2f} "
f"(+{result.k_after - result.k_before:,.2f})")
# ── Demo Mode ───────────────────────────────────────────────────────
def run_demo() -> None:
"""Run a demonstration of the AMM calculator."""
print("=" * 60)
print(" Constant Product AMM Calculator — Demo")
print("=" * 60)
# Create pool
pool = ConstantProductPool(
token_x="SOL",
token_y="USDC",
reserve_x=0,
reserve_y=0,
fee_rate=0.003,
lp_fee_share=0.88,
)
# Step 1: Initial liquidity
print("\n\n▸ STEP 1: Initial Liquidity Deposit")
print(" Alice deposits 100 SOL + 10,000 USDC")
pos_alice = pool.add_liquidity("Alice", 100, 10_000)
print(f" Alice receives {pos_alice.shares:,.4f} LP shares")
print_pool_state(pool)
# Step 2: Second LP
print("\n\n▸ STEP 2: Second LP Deposit")
print(" Bob deposits 50 SOL + 5,000 USDC (proportional)")
pos_bob = pool.add_liquidity("Bob", 50, 5_000)
print(f" Bob receives {pos_bob.shares:,.4f} LP shares")
print(f" Alice's share: {pos_alice.shares / pool.total_shares * 100:.1f}%")
print(f" Bob's share: {pos_bob.shares / pool.total_shares * 100:.1f}%")
print_pool_state(pool)
# Step 3: Series of trades
print("\n\n▸ STEP 3: Execute Trades")
trades = [
("x_for_y", 5, "Trader sells 5 SOL for USDC"),
("x_for_y", 10, "Trader sells 10 SOL for USDC"),
("y_for_x", 2000, "Trader buys SOL with 2,000 USDC"),
("x_for_y", 2, "Trader sells 2 SOL for USDC"),
("y_for_x", 500, "Trader buys SOL with 500 USDC"),
]
for direction, amount, description in trades:
print(f"\n {description}")
if direction == "x_for_y":
result = pool.swap_x_for_y(amount)
else:
result = pool.swap_y_for_x(amount)
print_trade_result(result)
print_pool_state(pool)
# Step 4: Show k growth from fees
print("\n\n▸ STEP 4: Fee Accrual Analysis")
initial_k = 100 * 10_000 # 1,000,000
print(f" Initial k: {initial_k:>16,.2f}")
print(f" Current k: {pool.k:>16,.2f}")
print(f" k growth: {(pool.k / initial_k - 1) * 100:>15.4f}%")
print(f" Accumulated fees: {pool.total_fees_x:,.4f} {pool.token_x} + "
f"{pool.total_fees_y:,.4f} {pool.token_y}")
# Step 5: LP position values
print("\n\n▸ STEP 5: LP Position Values")
alice_x, alice_y = pool.share_value(pos_alice.shares)
bob_x, bob_y = pool.share_value(pos_bob.shares)
print(f" Alice deposited: {pos_alice.deposit_x:,.4f} SOL + "
f"{pos_alice.deposit_y:,.2f} USDC")
print(f" Alice current: {alice_x:,.4f} SOL + {alice_y:,.2f} USDC")
alice_value = alice_x * pool.spot_price + alice_y
alice_deposit_value = pos_alice.deposit_x * 100 + pos_alice.deposit_y # original price
print(f" Alice value (USDC): {alice_value:,.2f} "
f"(deposited ~{alice_deposit_value:,.2f})")
print()
print(f" Bob deposited: {pos_bob.deposit_x:,.4f} SOL + "
f"{pos_bob.deposit_y:,.2f} USDC")
print(f" Bob current: {bob_x:,.4f} SOL + {bob_y:,.2f} USDC")
bob_value = bob_x * pool.spot_price + bob_y
bob_deposit_value = pos_bob.deposit_x * 100 + pos_bob.deposit_y
print(f" Bob value (USDC): {bob_value:,.2f} "
f"(deposited ~{bob_deposit_value:,.2f})")
# Step 6: Fee APR estimate
print("\n\n▸ STEP 6: Fee APR Estimate")
for daily_vol in [100_000, 500_000, 1_000_000]:
apr = pool.fee_apr_estimate(daily_vol)
print(f" Daily volume ${daily_vol:>10,} → Fee APR: {apr * 100:>7.2f}%")
# Step 7: Required input calculation
print("\n\n▸ STEP 7: Required Input Calculation")
desired_usdc = 1000
required_sol = pool.calculate_required_input_x(desired_usdc)
print(f" To receive exactly {desired_usdc:,} USDC, "
f"you need {required_sol:,.4f} SOL")
print(f" Effective price: {desired_usdc / required_sol:,.4f} USDC/SOL "
f"(spot: {pool.spot_price:,.4f})")
# Step 8: Price impact for various sizes
print("\n\n▸ STEP 8: Price Impact by Trade Size")
print(f" {'Trade Size':>12} {'Output':>12} {'Impact':>10} {'Eff. Price':>12}")
print(f" {'─' * 12} {'─' * 12} {'─' * 10} {'─' * 12}")
for size in [1, 5, 10, 25, 50, 100]:
impact = size / (pool.reserve_x + size) * 100
output = pool.reserve_y * size * (1 - pool.fee_rate) / (pool.reserve_x + size * (1 - pool.fee_rate))
eff_price = output / size
print(f" {size:>10.1f} {pool.token_x} {output:>10.2f} {pool.token_y} "
f"{impact:>8.2f}% {eff_price:>10.4f}")
# Step 9: Withdrawal
print("\n\n▸ STEP 9: LP Withdrawal")
print(f" Bob burns all {pos_bob.shares:,.4f} shares")
x_out, y_out = pool.remove_liquidity(pos_bob.shares)
print(f" Bob receives: {x_out:,.4f} {pool.token_x} + {y_out:,.2f} {pool.token_y}")
print_pool_state(pool)
print("\n" + "=" * 60)
print(" Demo Complete")
print("=" * 60)
# ── Interactive Mode ────────────────────────────────────────────────
def run_interactive() -> None:
"""Run interactive AMM calculator."""
print("=" * 60)
print(" Constant Product AMM Calculator — Interactive Mode")
print("=" * 60)
print("\nConfigure your pool:")
try:
token_x = input(" Token X name [SOL]: ").strip() or "SOL"
token_y = input(" Token Y name [USDC]: ").strip() or "USDC"
reserve_x = float(input(f" Initial {token_x} reserve [100]: ").strip() or "100")
reserve_y = float(input(f" Initial {token_y} reserve [10000]: ").strip() or "10000")
fee_pct = float(input(" Fee % [0.3]: ").strip() or "0.3")
except (ValueError, EOFError):
print("Invalid input, using defaults.")
token_x, token_y = "SOL", "USDC"
reserve_x, reserve_y = 100, 10_000
fee_pct = 0.3
pool = ConstantProductPool(
token_x=token_x,
token_y=token_y,
reserve_x=0,
reserve_y=0,
fee_rate=fee_pct / 100,
lp_fee_share=0.88,
)
pool.add_liquidity("initial", reserve_x, reserve_y)
print_pool_state(pool)
print("\nCommands:")
print(" sell_x <amount> — Sell token X for token Y")
print(" sell_y <amount> — Sell token Y for token X")
print(" need_y <amount> — Calculate X needed for specific Y output")
print(" add <x> <y> — Add liquidity")
print(" state — Show pool state")
print(" quit — Exit")
while True:
try:
cmd = input("\n> ").strip().lower()
except (EOFError, KeyboardInterrupt):
break
if not cmd:
continue
parts = cmd.split()
action = parts[0]
try:
if action == "quit" or action == "q":
break
elif action == "state":
print_pool_state(pool)
elif action == "sell_x" and len(parts) == 2:
result = pool.swap_x_for_y(float(parts[1]))
print_trade_result(result)
elif action == "sell_y" and len(parts) == 2:
result = pool.swap_y_for_x(float(parts[1]))
print_trade_result(result)
elif action == "need_y" and len(parts) == 2:
needed = pool.calculate_required_input_x(float(parts[1]))
print(f" Need {needed:,.4f} {pool.token_x} to get "
f"{float(parts[1]):,.4f} {pool.token_y}")
elif action == "add" and len(parts) == 3:
pos = pool.add_liquidity("user", float(parts[1]), float(parts[2]))
print(f" Received {pos.shares:,.4f} shares")
print_pool_state(pool)
else:
print(" Unknown command. Type 'quit' to exit.")
except (ValueError, ZeroDivisionError) as e:
print(f" Error: {e}")
print("\nFinal state:")
print_pool_state(pool)
# ── Main ────────────────────────────────────────────────────────────
def main() -> None:
"""Entry point."""
parser = argparse.ArgumentParser(
description="Constant Product AMM Calculator"
)
parser.add_argument(
"--demo",
action="store_true",
help="Run demo mode with pre-configured trades",
)
args = parser.parse_args()
if args.demo:
run_demo()
else:
run_interactive()
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Concentrated Liquidity (CLMM) Calculator.
Calculates position values, capital efficiency, liquidity amounts, and fee
earnings for concentrated liquidity AMM positions (Orca Whirlpool, Raydium CLMM,
Uniswap V3-style).
Usage:
python scripts/clmm_calculator.py
python scripts/clmm_calculator.py --demo
Dependencies:
None (pure math, standard library only)
Environment Variables:
None required
"""
import argparse
import math
from dataclasses import dataclass
from typing import Optional
# ── Data Models ─────────────────────────────────────────────────────
@dataclass
class CLMMPosition:
"""A concentrated liquidity position."""
liquidity: float
price_lower: float
price_upper: float
entry_price: float
@property
def tick_lower(self) -> int:
"""Lower tick boundary."""
return price_to_tick(self.price_lower)
@property
def tick_upper(self) -> int:
"""Upper tick boundary."""
return price_to_tick(self.price_upper)
@property
def range_width_pct(self) -> float:
"""Range width as percentage of midpoint."""
mid = (self.price_lower + self.price_upper) / 2
return (self.price_upper - self.price_lower) / mid * 100
@dataclass
class PositionValue:
"""Value of a CLMM position at a given price."""
price: float
amount_x: float
amount_y: float
total_value_y: float
in_range: bool
pct_token_x: float
pct_token_y: float
@dataclass
class RangeComparison:
"""Comparison of different range strategies."""
label: str
price_lower: float
price_upper: float
efficiency: float
liquidity: float
value_at_entry: float
value_at_minus10: float
value_at_plus10: float
il_at_minus10: float
il_at_plus10: float
fee_multiplier: float
# ── Core Math Functions ─────────────────────────────────────────────
def price_to_tick(price: float) -> int:
"""Convert price to tick index.
Args:
price: The price to convert.
Returns:
Tick index (rounded to nearest integer).
Raises:
ValueError: If price is non-positive.
"""
if price <= 0:
raise ValueError(f"Price must be positive, got {price}")
return round(math.log(price) / math.log(1.0001))
def tick_to_price(tick: int) -> float:
"""Convert tick index to price.
Args:
tick: The tick index.
Returns:
Price at the given tick.
"""
return 1.0001 ** tick
def capital_efficiency(price_lower: float, price_upper: float) -> float:
"""Calculate capital efficiency ratio vs full-range position.
A position in range [P_lower, P_upper] provides the same market depth
as a full-range position with `efficiency` times more capital.
Args:
price_lower: Lower bound of the price range.
price_upper: Upper bound of the price range.
Returns:
Capital efficiency multiplier.
Raises:
ValueError: If price_lower >= price_upper or either is non-positive.
"""
if price_lower <= 0 or price_upper <= 0:
raise ValueError("Prices must be positive")
if price_lower >= price_upper:
raise ValueError("price_lower must be less than price_upper")
ratio = math.sqrt(price_upper / price_lower)
if ratio <= 1:
return float("inf")
return ratio / (ratio - 1)
def liquidity_from_amounts(
price_current: float,
price_lower: float,
price_upper: float,
amount_x: float,
amount_y: float,
) -> float:
"""Calculate liquidity L from deposit amounts.
Args:
price_current: Current pool price (Y per X).
price_lower: Lower price bound.
price_upper: Upper price bound.
amount_x: Amount of token X to deposit.
amount_y: Amount of token Y to deposit.
Returns:
Liquidity value L. Uses min(L_from_x, L_from_y) for balanced deposit.
Raises:
ValueError: If prices are invalid.
"""
if price_lower >= price_upper:
raise ValueError("price_lower must be less than price_upper")
if price_current <= 0:
raise ValueError("Current price must be positive")
sqrt_p = math.sqrt(price_current)
sqrt_pa = math.sqrt(price_lower)
sqrt_pb = math.sqrt(price_upper)
if price_current <= price_lower:
# All token X
if amount_x <= 0:
return 0.0
return amount_x / (1 / sqrt_pa - 1 / sqrt_pb)
elif price_current >= price_upper:
# All token Y
if amount_y <= 0:
return 0.0
return amount_y / (sqrt_pb - sqrt_pa)
else:
# In range: both tokens
l_from_x = amount_x / (1 / sqrt_p - 1 / sqrt_pb) if amount_x > 0 else float("inf")
l_from_y = amount_y / (sqrt_p - sqrt_pa) if amount_y > 0 else float("inf")
return min(l_from_x, l_from_y)
def amounts_from_liquidity(
liquidity: float,
price_current: float,
price_lower: float,
price_upper: float,
) -> tuple[float, float]:
"""Calculate token amounts for a given liquidity and price.
Args:
liquidity: The liquidity value L.
price_current: Current pool price.
price_lower: Lower price bound.
price_upper: Upper price bound.
Returns:
Tuple of (amount_x, amount_y).
"""
sqrt_p = math.sqrt(price_current)
sqrt_pa = math.sqrt(price_lower)
sqrt_pb = math.sqrt(price_upper)
if price_current <= price_lower:
amount_x = liquidity * (1 / sqrt_pa - 1 / sqrt_pb)
amount_y = 0.0
elif price_current >= price_upper:
amount_x = 0.0
amount_y = liquidity * (sqrt_pb - sqrt_pa)
else:
amount_x = liquidity * (1 / sqrt_p - 1 / sqrt_pb)
amount_y = liquidity * (sqrt_p - sqrt_pa)
return amount_x, amount_y
def position_value(
liquidity: float,
price_current: float,
price_lower: float,
price_upper: float,
) -> PositionValue:
"""Calculate the full value of a CLMM position.
Args:
liquidity: The liquidity value L.
price_current: Current pool price (Y per X).
price_lower: Lower price bound.
price_upper: Upper price bound.
Returns:
PositionValue with token amounts and total value.
"""
amount_x, amount_y = amounts_from_liquidity(
liquidity, price_current, price_lower, price_upper
)
total_y = amount_x * price_current + amount_y
in_range = price_lower < price_current < price_upper
pct_x = (amount_x * price_current / total_y * 100) if total_y > 0 else 0
pct_y = (amount_y / total_y * 100) if total_y > 0 else 0
return PositionValue(
price=price_current,
amount_x=amount_x,
amount_y=amount_y,
total_value_y=total_y,
in_range=in_range,
pct_token_x=pct_x,
pct_token_y=pct_y,
)
def impermanent_loss_pct(price_ratio: float) -> float:
"""Calculate impermanent loss for a full-range position.
Args:
price_ratio: New price / entry price.
Returns:
IL as a positive percentage (e.g., 5.72 for 5.72% loss).
"""
if price_ratio <= 0:
return 100.0
il = 2 * math.sqrt(price_ratio) / (1 + price_ratio) - 1
return abs(il) * 100
def clmm_impermanent_loss(
liquidity: float,
entry_price: float,
current_price: float,
price_lower: float,
price_upper: float,
) -> float:
"""Calculate impermanent loss for a CLMM position.
Compares position value to holding the initial tokens.
Args:
liquidity: Position liquidity L.
entry_price: Price when position was opened.
current_price: Current price.
price_lower: Lower price bound.
price_upper: Upper price bound.
Returns:
IL as a percentage (positive = loss vs holding).
"""
# Value at entry
entry_x, entry_y = amounts_from_liquidity(
liquidity, entry_price, price_lower, price_upper
)
hold_value = entry_x * current_price + entry_y
# Current position value
current_x, current_y = amounts_from_liquidity(
liquidity, current_price, price_lower, price_upper
)
position_value_now = current_x * current_price + current_y
if hold_value == 0:
return 0.0
return (1 - position_value_now / hold_value) * 100
def estimate_fee_earnings(
your_liquidity: float,
total_liquidity_in_range: float,
daily_volume: float,
fee_rate: float,
days: int = 1,
) -> float:
"""Estimate fee earnings for a CLMM position.
Args:
your_liquidity: Your L in the active range.
total_liquidity_in_range: Total L from all LPs in the active range.
daily_volume: Average daily trading volume (in token Y terms).
fee_rate: Fee rate as decimal (e.g., 0.003 for 0.3%).
days: Number of days to estimate.
Returns:
Estimated fee earnings in token Y terms.
"""
if total_liquidity_in_range == 0:
return 0.0
your_share = your_liquidity / total_liquidity_in_range
daily_fees = daily_volume * fee_rate
return daily_fees * your_share * days
# ── Display Functions ───────────────────────────────────────────────
def print_position_value(pv: PositionValue, token_x: str, token_y: str) -> None:
"""Print position value details."""
status = "IN RANGE" if pv.in_range else "OUT OF RANGE"
print(f" Price: {pv.price:,.4f} {token_y}/{token_x} [{status}]")
print(f" {token_x}: {pv.amount_x:>12,.6f} ({pv.pct_token_x:>5.1f}%)")
print(f" {token_y}: {pv.amount_y:>12,.4f} ({pv.pct_token_y:>5.1f}%)")
print(f" Total ({token_y}): {pv.total_value_y:>12,.4f}")
def print_range_comparison_table(comparisons: list[RangeComparison], token_y: str) -> None:
"""Print a comparison table of range strategies."""
print(f"\n {'Strategy':<18} {'Range':>16} {'Efficiency':>11} "
f"{'Fee Mult':>9} {'IL@-10%':>8} {'IL@+10%':>8}")
print(f" {'─' * 18} {'─' * 16} {'─' * 11} {'─' * 9} {'─' * 8} {'─' * 8}")
for c in comparisons:
range_str = f"{c.price_lower:.1f}–{c.price_upper:.1f}"
print(f" {c.label:<18} {range_str:>16} {c.efficiency:>10.1f}x "
f"{c.fee_multiplier:>8.1f}x {c.il_at_minus10:>7.2f}% {c.il_at_plus10:>7.2f}%")
# ── Demo Mode ───────────────────────────────────────────────────────
def run_demo() -> None:
"""Run a demonstration of CLMM calculations."""
print("=" * 65)
print(" Concentrated Liquidity (CLMM) Calculator — Demo")
print("=" * 65)
token_x = "SOL"
token_y = "USDC"
current_price = 100.0 # 100 USDC per SOL
deposit_value = 10_000.0 # $10,000 total deposit
# Step 1: Capital efficiency comparison
print("\n\n--- STEP 1: Capital Efficiency by Range Width ---")
print(f"\n Base price: {current_price} {token_y}/{token_x}")
print(f" Deposit value: {deposit_value:,.0f} {token_y}\n")
ranges = [
("Ultra-tight ±2%", 0.02),
("Tight ±5%", 0.05),
("Medium ±10%", 0.10),
("Wide ±25%", 0.25),
("Very wide ±50%", 0.50),
("Extra wide ±90%", 0.90),
]
print(f" {'Range':<22} {'Lower':>8} {'Upper':>8} {'Efficiency':>11}")
print(f" {'─' * 22} {'─' * 8} {'─' * 8} {'─' * 11}")
for label, pct in ranges:
p_low = current_price * (1 - pct)
p_high = current_price * (1 + pct)
eff = capital_efficiency(p_low, p_high)
print(f" {label:<22} {p_low:>8.2f} {p_high:>8.2f} {eff:>10.1f}x")
# Step 2: Position value at different prices
print("\n\n--- STEP 2: Position Value Across Prices ---")
# Create a position with ±25% range
p_lower = 75.0
p_upper = 125.0
# Determine liquidity from a $10,000 deposit at current price
# Half in each token at current price
deposit_x = deposit_value / 2 / current_price # 50 SOL
deposit_y = deposit_value / 2 # 5,000 USDC
liq = liquidity_from_amounts(current_price, p_lower, p_upper, deposit_x, deposit_y)
print(f"\n Position: L = {liq:,.2f}")
print(f" Range: [{p_lower:.2f}, {p_upper:.2f}] {token_y}/{token_x}")
print(f" Deposit: {deposit_x:,.4f} {token_x} + {deposit_y:,.2f} {token_y}")
prices_to_check = [50, 60, 70, 75, 80, 90, 100, 110, 120, 125, 130, 140, 150]
print(f"\n {'Price':>8} {'Status':>12} {token_x + ' Amount':>14} "
f"{token_y + ' Amount':>14} {'Total (' + token_y + ')':>14} {'IL':>8}")
print(f" {'─' * 8} {'─' * 12} {'─' * 14} {'─' * 14} {'─' * 14} {'─' * 8}")
for p in prices_to_check:
pv = position_value(liq, p, p_lower, p_upper)
il = clmm_impermanent_loss(liq, current_price, p, p_lower, p_upper)
status = "IN RANGE" if pv.in_range else "out"
print(f" {p:>8.2f} {status:>12} {pv.amount_x:>14.4f} "
f"{pv.amount_y:>14.2f} {pv.total_value_y:>14.2f} {il:>7.2f}%")
# Step 3: Range strategy comparison
print("\n\n--- STEP 3: Range Strategy Comparison ---")
print(f" All positions: {deposit_value:,.0f} {token_y} deposit at "
f"price {current_price} {token_y}/{token_x}")
comparisons: list[RangeComparison] = []
strategies = [
("Tight ±5%", 0.05),
("Medium ±10%", 0.10),
("Wide ±25%", 0.25),
("Very Wide ±50%", 0.50),
("Full Range ±99%", 0.99),
]
for label, pct in strategies:
pl = current_price * (1 - pct)
pu = current_price * (1 + pct)
eff = capital_efficiency(pl, pu)
dep_x = deposit_value / 2 / current_price
dep_y = deposit_value / 2
l = liquidity_from_amounts(current_price, pl, pu, dep_x, dep_y)
val_entry = position_value(l, current_price, pl, pu).total_value_y
val_m10 = position_value(l, current_price * 0.9, pl, pu).total_value_y
val_p10 = position_value(l, current_price * 1.1, pl, pu).total_value_y
il_m10 = clmm_impermanent_loss(l, current_price, current_price * 0.9, pl, pu)
il_p10 = clmm_impermanent_loss(l, current_price, current_price * 1.1, pl, pu)
comparisons.append(RangeComparison(
label=label,
price_lower=pl,
price_upper=pu,
efficiency=eff,
liquidity=l,
value_at_entry=val_entry,
value_at_minus10=val_m10,
value_at_plus10=val_p10,
il_at_minus10=il_m10,
il_at_plus10=il_p10,
fee_multiplier=eff, # Fee multiplier equals efficiency
))
print_range_comparison_table(comparisons, token_y)
# Step 4: Fee earnings estimation
print("\n\n--- STEP 4: Fee Earnings Estimation ---")
print(f" Assumptions:")
print(f" Daily volume: $500,000")
print(f" Fee tier: 0.30%")
print(f" Your deposit: ${deposit_value:,.0f}")
daily_volume = 500_000
fee_rate = 0.003
print(f"\n {'Strategy':<18} {'Your L':>12} {'Daily Fees':>12} "
f"{'Monthly':>12} {'Ann. APR':>10}")
print(f" {'─' * 18} {'─' * 12} {'─' * 12} {'─' * 12} {'─' * 10}")
for c in comparisons:
# Assume total liquidity in range = 10x yours
total_l = c.liquidity * 10
daily_fees = estimate_fee_earnings(
c.liquidity, total_l, daily_volume, fee_rate
)
monthly = daily_fees * 30
apr = (daily_fees * 365 / deposit_value) * 100
print(f" {c.label:<18} {c.liquidity:>12,.0f} ${daily_fees:>10,.2f} "
f"${monthly:>10,.2f} {apr:>9.1f}%")
print("\n Note: Tighter ranges earn more fees per $ when in range,")
print(" but earn zero fees when price moves outside the range.")
# Step 5: Tick math demonstration
print("\n\n--- STEP 5: Tick Math ---")
print(f"\n Price → Tick → Back to Price:")
test_prices = [50.0, 75.0, 100.0, 125.0, 150.0, 200.0]
print(f" {'Price':>10} {'Tick':>10} {'Reconstructed':>14} {'Error':>10}")
print(f" {'─' * 10} {'─' * 10} {'─' * 14} {'─' * 10}")
for p in test_prices:
t = price_to_tick(p)
p_back = tick_to_price(t)
error = abs(p - p_back) / p * 100
print(f" {p:>10.4f} {t:>10d} {p_back:>14.4f} {error:>9.4f}%")
# Step 6: Deposit amount calculation
print("\n\n--- STEP 6: Required Deposit Amounts ---")
print(f" Target: $10,000 total position value")
print(f" Current price: {current_price} {token_y}/{token_x}\n")
for label, pct in [("±5%", 0.05), ("±10%", 0.10), ("±25%", 0.25), ("±50%", 0.50)]:
pl = current_price * (1 - pct)
pu = current_price * (1 + pct)
# Calculate amounts for a balanced deposit
# Start with equal-value split and compute L
dx = deposit_value / 2 / current_price
dy = deposit_value / 2
l = liquidity_from_amounts(current_price, pl, pu, dx, dy)
# Get the actual balanced amounts from L
actual_x, actual_y = amounts_from_liquidity(l, current_price, pl, pu)
actual_value = actual_x * current_price + actual_y
print(f" Range {label} [{pl:.2f}–{pu:.2f}]:")
print(f" Deposit: {actual_x:,.4f} {token_x} ({actual_x * current_price:,.2f} {token_y}) "
f"+ {actual_y:,.2f} {token_y}")
print(f" Total value: {actual_value:,.2f} {token_y}")
print(f" Split: {actual_x * current_price / actual_value * 100:.1f}% "
f"{token_x} / {actual_y / actual_value * 100:.1f}% {token_y}")
print()
print("=" * 65)
print(" Demo Complete")
print("=" * 65)
# ── Interactive Mode ────────────────────────────────────────────────
def run_interactive() -> None:
"""Run interactive CLMM calculator."""
print("=" * 65)
print(" Concentrated Liquidity Calculator — Interactive Mode")
print("=" * 65)
print("\nConfigure your position:")
try:
token_x = input(" Token X name [SOL]: ").strip() or "SOL"
token_y = input(" Token Y name [USDC]: ").strip() or "USDC"
price = float(input(f" Current price ({token_y}/{token_x}) [100]: ").strip() or "100")
p_lower = float(input(f" Range lower ({token_y}) [75]: ").strip() or "75")
p_upper = float(input(f" Range upper ({token_y}) [125]: ").strip() or "125")
amount_x = float(input(f" Deposit {token_x} [50]: ").strip() or "50")
amount_y = float(input(f" Deposit {token_y} [5000]: ").strip() or "5000")
except (ValueError, EOFError):
print("Invalid input, using defaults.")
token_x, token_y = "SOL", "USDC"
price, p_lower, p_upper = 100, 75, 125
amount_x, amount_y = 50, 5000
liq = liquidity_from_amounts(price, p_lower, p_upper, amount_x, amount_y)
eff = capital_efficiency(p_lower, p_upper)
print(f"\n Liquidity: {liq:,.2f}")
print(f" Capital efficiency: {eff:.1f}x")
pv = position_value(liq, price, p_lower, p_upper)
print_position_value(pv, token_x, token_y)
print("\nCommands:")
print(" price <p> — Show position value at price p")
print(" sweep — Show values across price range")
print(" fees <vol> — Estimate daily fees for given volume")
print(" efficiency — Show capital efficiency")
print(" quit — Exit")
while True:
try:
cmd = input("\n> ").strip().lower()
except (EOFError, KeyboardInterrupt):
break
if not cmd:
continue
parts = cmd.split()
action = parts[0]
try:
if action in ("quit", "q"):
break
elif action == "price" and len(parts) == 2:
p = float(parts[1])
pv = position_value(liq, p, p_lower, p_upper)
il = clmm_impermanent_loss(liq, price, p, p_lower, p_upper)
print_position_value(pv, token_x, token_y)
print(f" IL vs holding: {il:.2f}%")
elif action == "sweep":
step = (p_upper - p_lower) / 10
for i in range(15):
p = p_lower - 2 * step + i * step
if p <= 0:
continue
pv = position_value(liq, p, p_lower, p_upper)
status = "*" if pv.in_range else " "
print(f" {status} {p:>8.2f}: {pv.amount_x:>10.4f} {token_x} + "
f"{pv.amount_y:>10.2f} {token_y} = "
f"{pv.total_value_y:>10.2f} total")
elif action == "fees" and len(parts) == 2:
vol = float(parts[1])
total_l = liq * 10 # Assume 10x
daily = estimate_fee_earnings(liq, total_l, vol, 0.003)
print(f" Daily fees: ${daily:,.2f} (assuming 10% of range liquidity)")
print(f" Monthly: ${daily * 30:,.2f}")
print(f" Annual APR: {daily * 365 / pv.total_value_y * 100:.1f}%")
elif action == "efficiency":
print(f" Efficiency: {eff:.1f}x vs full range")
print(f" Range: [{p_lower:.2f}, {p_upper:.2f}]")
print(f" Width: {(p_upper - p_lower) / ((p_upper + p_lower) / 2) * 100:.1f}%")
else:
print(" Unknown command. Type 'quit' to exit.")
except (ValueError, ZeroDivisionError) as e:
print(f" Error: {e}")
# ── Main ────────────────────────────────────────────────────────────
def main() -> None:
"""Entry point."""
parser = argparse.ArgumentParser(
description="Concentrated Liquidity (CLMM) Calculator"
)
parser.add_argument(
"--demo",
action="store_true",
help="Run demo mode with pre-configured examples",
)
args = parser.parse_args()
if args.demo:
run_demo()
else:
run_interactive()
if __name__ == "__main__":
main()