
Analyzing Liquidity Pools
- 43 installs
- 2.6k repo stars
- Updated August 5, 2026
- jeremylongshore/claude-code-plugins-plus-skills
Analyzes DEX liquidity pools across Uniswap, Curve, and Balancer for TVL, volume, fees, impermanent loss, and LP profitability.
About
Evaluates DEX liquidity pools for TVL, trading volume, fee income, and impermanent-loss risk, comparing pools across protocols and chains. A developer uses it to assess LP opportunities and project returns before providing liquidity.
- Pool analysis by address or token pair via pool_analyzer.py
- Impermanent-loss scenarios and fee-APR return estimates
Analyzing Liquidity Pools by the numbers
- 43 all-time installs (skills.sh)
- Ranked #216 of 479 Web3 & Blockchain skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/jeremylongshore/claude-code-plugins-plus-skills --skill analyzing-liquidity-poolsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 43 |
|---|---|
| repo stars | ★ 2.6k |
| Last updated | August 5, 2026 |
| Repository | jeremylongshore/claude-code-plugins-plus-skills ↗ |
What it does
Analyzes DEX liquidity pools across Uniswap, Curve, and Balancer for TVL, volume, fees, impermanent loss, and LP profitability.
Files
Analyzing Liquidity Pools
Overview
Analyze DEX liquidity pools across Uniswap, Curve, and Balancer to evaluate TVL, trading volume, fee income, and impermanent loss risk. Compare pools across protocols and chains to identify optimal LP opportunities.
Prerequisites
- Python 3.8+ installed
- Internet access for subgraph/API queries
- Understanding of liquidity providing concepts (IL, fee tiers, TVL)
Instructions
1. Analyze a specific pool by address or token pair:
python ${CLAUDE_SKILL_DIR}/scripts/pool_analyzer.py --pool 0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640
python ${CLAUDE_SKILL_DIR}/scripts/pool_analyzer.py --pair ETH/USDC --protocol uniswap-v32. Calculate impermanent loss for a price change:
python ${CLAUDE_SKILL_DIR}/scripts/pool_analyzer.py --il-calc --entry-price 2000 --current-price 3000 # 2000/3000 = ETH price at entry/now in USDProject IL for various scenarios:
python ${CLAUDE_SKILL_DIR}/scripts/pool_analyzer.py --il-scenarios --token-pair ETH/USDC3. Estimate LP returns with fee APR and position projections:
python ${CLAUDE_SKILL_DIR}/scripts/pool_analyzer.py --pool [address] --detailed
python ${CLAUDE_SKILL_DIR}/scripts/pool_analyzer.py --pool [address] --position 10000 # 10000 = LP position size in USD4. Compare pools across protocols or fee tiers:
python ${CLAUDE_SKILL_DIR}/scripts/pool_analyzer.py --compare --pair ETH/USDC --protocols uniswap-v3,curve,balancer
python ${CLAUDE_SKILL_DIR}/scripts/pool_analyzer.py --compare --pair ETH/USDC --fee-tiers 0.05,0.30,1.005. Export results to JSON or CSV:
python ${CLAUDE_SKILL_DIR}/scripts/pool_analyzer.py --pool [address] --format json --output pool_analysis.json
python ${CLAUDE_SKILL_DIR}/scripts/pool_analyzer.py --compare --pair ETH/USDC --format csv --output pools.csvOutput
Pool analysis reports include chain, TVL, 24h volume, fee tier, fee APR, volume/TVL ratio, and token composition with current price. Impermanent loss reports show IL percentage, dollar impact, HODL vs LP value comparison, and breakeven analysis with days-to-recover based on fee income.
See ${CLAUDE_SKILL_DIR}/references/implementation.md for detailed output format examples.
Error Handling
| Error | Cause | Fix |
|---|---|---|
| Pool not found | Wrong address or chain | Verify address and target chain |
| Subgraph timeout | API latency or downtime | Uses cached data with warning |
| Invalid pair | Unsupported protocol | Check supported protocols list |
Examples
Analyze top ETH/USDC pool - Full TVL, volume, and fee breakdown on Uniswap V3:
python pool_analyzer.py --pair ETH/USDC --protocol uniswap-v3 --chain ethereumCalculate IL for 2x price increase - See dollar impact vs holding:
python pool_analyzer.py --il-calc --entry-price 100 --current-price 200 # 100/200 = token price at entry/now in USDCompare Uniswap fee tiers - Find optimal fee tier for ETH/USDC:
python pool_analyzer.py --compare --pair ETH/USDC --fee-tiers 0.05,0.30,1.00Export all ETH pairs - Dump pool data for further analysis:
python pool_analyzer.py --token ETH --format json --output eth_pools.jsonResources
- The Graph: https://thegraph.com/ - Subgraph queries
- Uniswap Info: https://info.uniswap.org/ - Pool explorer
- DeFiLlama: https://defillama.com/ - TVL data
${CLAUDE_SKILL_DIR}/references/implementation.md- Detailed output formats, configuration, cross-protocol comparison guide
ARD: Liquidity Pool Analyzer
Part of Tons of Skills by Intent Solutions | jeremylongshore.com
Architecture Pattern
Pattern: Multi-Source Data Aggregation + Analysis Pipeline Type: Fetch → Normalize → Calculate → Analyze → Output
┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐
│ Subgraphs │───▶│ Normalizer │───▶│ IL Calculator│───▶│ Analyzer │
│ + APIs │ │ (Pool Data) │ │ (Math) │ │ (Output) │
└─────────────┘ └──────────────┘ └─────────────┘ └──────────────┘
│ │ │ │
▼ ▼ ▼ ▼
Raw Pool Data Normalized Pools IL Scenarios Pool Report
(reserves, vol) (TVL, APR, fees) (loss, breakeven) (table/JSON)Workflow
Step 1: Fetch Pool Data
Query subgraphs and APIs for pool information.
Step 2: Normalize Metrics
- Convert reserves to USD TVL
- Calculate fee APR from volume
- Standardize across protocols
Step 3: Calculate IL/Returns
- Impermanent loss for price changes
- Fee income projections
- Net APY estimates
Step 4: Analyze and Output
- Pool health metrics
- Comparison tables
- Risk warnings
Data Flow
Input: Processing: Output:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ User Criteria │ │ Data Aggregator │ │ Pool Report │
│ - Pool address │──────▶│ - The Graph │ │ - TVL/Volume │
│ - Token pair │ │ - DeFiLlama │ │ - Fee APR │
│ - Chain │ │ - CoinGecko │ │ - IL scenarios │
│ - Protocol │ └────────┬────────┘ │ - Risk metrics │
└─────────────────┘ │ │ │
┌────────▼────────┐ │ IL Calculator │
│ Pool Normalizer │ │ - Entry/exit IL │
│ - TVL calc │────────▶│ - Breakeven │
│ - Fee rates │ │ - Projections │
│ - Token ratios │ │ │
└────────┬────────┘ │ Comparison │
┌────────▼────────┐ │ - Cross-pool │
│ IL Calculator │ │ - Cross-protocol│
│ - Price change │ └─────────────────┘
│ - Fee offset │
│ - Net returns │
└─────────────────┘Directory Structure
plugins/crypto/liquidity-pool-analyzer/skills/analyzing-liquidity-pools/
├── PRD.md # Product requirements
├── ARD.md # This file
├── SKILL.md # Core instructions
├── scripts/
│ ├── pool_analyzer.py # Main CLI entry point
│ ├── pool_fetcher.py # Subgraph/API data fetching
│ ├── il_calculator.py # Impermanent loss math
│ ├── pool_metrics.py # TVL, fees, APR calculations
│ └── formatters.py # Output formatting
├── references/
│ ├── errors.md # Error handling guide
│ ├── examples.md # Usage examples
│ └── protocols.md # Supported DEX protocols
└── config/
└── settings.yaml # API endpoints, protocol configsComponent Design
1. Pool Fetcher (pool_fetcher.py)
Purpose: Fetch pool data from subgraphs and APIs.
Data Sources:
| Source | Data | Rate Limit |
|---|---|---|
| The Graph | Pool reserves, swaps, ticks | 100/min |
| DeFiLlama | TVL, volume | Generous |
| CoinGecko | Token prices | 10-30/min |
Subgraph Queries:
# Uniswap V3 Pool Query
query Pool($id: ID!) {
pool(id: $id) {
token0 { symbol, decimals }
token1 { symbol, decimals }
feeTier
liquidity
sqrtPrice
tick
totalValueLockedUSD
volumeUSD
}
}Caching Strategy:
- Memory cache: 30 seconds for hot pools
- File cache: 5 minutes for historical data
2. IL Calculator (il_calculator.py)
Purpose: Calculate impermanent loss and breakeven metrics.
Core Formulas:
# Impermanent Loss Formula
# price_ratio = new_price / original_price
IL = 2 * sqrt(price_ratio) / (1 + price_ratio) - 1
# Value if held (no LP)
value_held = (amount0 * price0_new) + (amount1 * price1_new)
# Value in LP
value_lp = value_held * (1 + IL) # IL is negative
# Breakeven Volume (for fee tier)
# days_to_breakeven = (IL_percent * TVL) / (daily_volume * fee_tier)Output:
{
"entry_price": 2000,
"current_price": 3000,
"price_change": 50.0, # percentage
"il_percent": -5.72, # negative = loss
"value_if_held": 2500,
"value_in_lp": 2357,
"il_usd": -143,
"breakeven_days": 45,
}3. Pool Metrics (pool_metrics.py)
Purpose: Calculate derived metrics from raw pool data.
Calculations:
# TVL from reserves
tvl_usd = reserve0 * price0 + reserve1 * price1
# Fee APR (annualized)
daily_fees = volume_24h * fee_tier
fee_apr = (daily_fees / tvl_usd) * 365 * 100
# Volume/TVL Ratio (efficiency)
volume_tvl_ratio = volume_24h / tvl_usd
# Token Weight (for imbalance detection)
weight_token0 = (reserve0 * price0) / tvl_usdHealth Indicators:
| Metric | Healthy | Warning | Critical |
|---|---|---|---|
| Volume/TVL | > 0.05 | 0.01-0.05 | < 0.01 |
| Token Balance | 40-60% | 30-40% or 60-70% | < 30% or > 70% |
| Fee APR | > 5% | 2-5% | < 2% |
4. Formatters (formatters.py)
Purpose: Format output for display.
Formats:
- Table: Pool metrics summary
- JSON: Structured data for analysis
- Report: Detailed pool analysis
API Integration
The Graph (Primary)
Endpoints:
Uniswap V3 Ethereum: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3
Uniswap V3 Arbitrum: https://api.thegraph.com/subgraphs/name/ianlapham/uniswap-v3-arbitrum
Curve: https://api.thegraph.com/subgraphs/name/curvefi/curve
Balancer V2: https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2Query Pattern:
query = """
{
pools(where: {id: $pool_id}) {
token0 { symbol }
token1 { symbol }
totalValueLockedUSD
volumeUSD
}
}
"""DeFiLlama Fallback
Endpoints:
GET /pools # All DEX pools
GET /pool/{poolId} # Specific poolError Handling Strategy
| Error Type | Handling | User Message |
|---|---|---|
| Subgraph timeout | Retry 2x, use DeFiLlama | "Using cached/fallback data" |
| Pool not found | Check chain, validate address | "Pool not found on [chain]" |
| Price unavailable | Use pool ratio as backup | "Using pool price (oracle unavailable)" |
| Invalid address | Validate format | "Invalid pool address format" |
Caching Architecture
Cache Layer:
┌─────────────────────────────────────────┐
│ Memory Cache │
│ (current session, 30 sec TTL) │
├─────────────────────────────────────────┤
│ File Cache │
│ (~/.lp_analyzer_cache.json, 5 min TTL) │
├─────────────────────────────────────────┤
│ Subgraph/API │
│ (fresh data when cache miss/stale) │
└─────────────────────────────────────────┘Performance
| Operation | Target | Constraint |
|---|---|---|
| Subgraph query | < 2 seconds | Network, rate limits |
| IL calculation | < 50ms | In-memory math |
| Pool metrics | < 100ms | Simple calculations |
| Total response | < 5 seconds | Full pipeline |
Security Considerations
- No wallet connections
- No private key handling
- Read-only subgraph queries
- No smart contract interactions
- Cache contains only public pool data
Testing Strategy
Unit Tests
- IL formula validation
- Fee APR calculations
- TVL from reserves
Integration Tests
- Subgraph query handling
- Multi-protocol normalization
- Error fallback scenarios
Test Data
{
"pool": {
"address": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
"token0": "USDC",
"token1": "WETH",
"fee_tier": 0.0005,
"tvl": 500000000,
"volume_24h": 100000000
}
}Dependencies
Required:
requests- HTTP client for APIsjson- Data handling
Optional:
web3- Direct RPC calls (fallback)
Supported Protocols
DEXs
- Uniswap V2, V3
- SushiSwap
- Curve
- Balancer V2
Chains
- Ethereum
- Arbitrum
- Polygon
- Optimism
- BSC
# Liquidity Pool Analyzer Configuration
# Version: 2.0.0
# API Configuration
api:
# The Graph Subgraphs
subgraphs:
uniswap-v3-ethereum: "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3"
uniswap-v3-arbitrum: "https://api.thegraph.com/subgraphs/name/ianlapham/uniswap-v3-arbitrum"
uniswap-v3-polygon: "https://api.thegraph.com/subgraphs/name/ianlapham/uniswap-v3-polygon"
uniswap-v2-ethereum: "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2"
curve-ethereum: "https://api.thegraph.com/subgraphs/name/curvefi/curve"
balancer-v2: "https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2"
# DeFiLlama (fallback)
defillama:
pools_url: "https://yields.llama.fi/pools"
timeout: 30
# CoinGecko (token prices)
coingecko:
base_url: "https://api.coingecko.com/api/v3"
timeout: 15
rate_limit: 30 # requests per minute
# Cache Configuration
cache:
enabled: true
file: "~/.lp_analyzer_cache.json"
ttl:
pools: 300 # 5 minutes
prices: 60 # 1 minute
metadata: 3600 # 1 hour
# Default Settings
defaults:
chain: "ethereum"
protocol: "uniswap-v3"
top: 10
min_tvl: 0
format: "table"
# Fee Tiers (common configurations)
fee_tiers:
uniswap_v3:
- 0.0001 # 0.01%
- 0.0005 # 0.05%
- 0.003 # 0.30%
- 0.01 # 1.00%
curve:
- 0.0004 # 0.04%
balancer:
- 0.0001
- 0.001
- 0.003
# Health Indicator Thresholds
health:
tvl:
large: 100000000 # $100M+
medium: 10000000 # $10M+
small: 1000000 # $1M+
micro: 0 # <$1M
volume_tvl_ratio:
very_high: 0.5
high: 0.1
medium: 0.05
low: 0.01
fee_apr:
high: 20
medium: 5
low: 1
# IL Warning Thresholds
il:
warning_threshold: 5 # Warn if IL > 5%
critical_threshold: 10 # Critical if IL > 10%
# Supported Chains
chains:
- ethereum
- arbitrum
- polygon
- optimism
- bsc
- avalanche
- base
# Supported Protocols
protocols:
- uniswap-v2
- uniswap-v3
- sushiswap
- curve
- balancer
- pancakeswap
- quickswap
# Logging
logging:
verbose: false
log_file: null
log_level: "INFO"
PRD: Liquidity Pool Analyzer
Summary
One-liner: Analyze DEX liquidity pools for TVL, volume, fees, impermanent loss, and LP profitability.
Domain: Cryptocurrency / DeFi / Liquidity Providing Users: Liquidity Providers, DeFi Traders, Protocol Analysts
Problem Statement
Liquidity providing on DEXs involves complex trade-offs:
- Understanding TVL distribution and pool depth
- Calculating potential impermanent loss for different price scenarios
- Analyzing fee income vs. IL to determine profitability
- Comparing pools across protocols and chains
- Monitoring pool health and detecting risks (rug pulls, imbalanced pools)
Users need a tool that aggregates pool data, calculates IL scenarios, estimates LP returns, and identifies optimal pools based on their risk tolerance.
User Personas
Persona 1: Active Liquidity Provider (Marcus)
- Profile: Experienced DeFi user with $20K-$200K providing liquidity
- Pain Points: Manually tracking multiple positions, calculating IL, comparing fee APRs
- Goals: Maximize fee income while managing IL risk, find optimal entry/exit points
Persona 2: DeFi Researcher (Luna)
- Profile: Analyst studying protocol health and pool dynamics
- Pain Points: Gathering data from multiple sources, analyzing pool composition trends
- Goals: Monitor protocol TVL, identify anomalies, track whale movements
Persona 3: Arbitrage Bot Operator (Dev)
- Profile: Technical trader running automated strategies
- Pain Points: Need real-time pool reserves, slippage estimates, gas optimization
- Goals: Find pools with significant depth, minimize slippage, optimize trade routes
User Stories
US-1: Analyze Pool Metrics (Critical)
As a liquidity provider I want to see comprehensive metrics for a specific pool So that I can evaluate it before adding liquidity
Acceptance Criteria:
- Shows TVL, 24h volume, and fee tier
- Displays current token ratio and prices
- Shows historical volume/TVL ratio
- Includes pool contract address and creation date
US-2: Calculate Impermanent Loss (Critical)
As a liquidity provider I want to calculate IL for various price scenarios So that I can understand my risk exposure
Acceptance Criteria:
- Calculate IL for specific price changes
- Show breakeven fee income needed
- Compare IL vs. holding both tokens
- Project IL over time periods
US-3: Estimate LP Returns (High)
As a liquidity provider I want to estimate my potential returns from fees So that I can decide if the IL risk is worth it
Acceptance Criteria:
- Calculate fee APR from historical volume
- Show fee income projection for position size
- Factor in reward tokens if applicable
- Display net APY after estimated IL
US-4: Compare Pools (High)
As a DeFi researcher I want to compare similar pools across protocols So that I can identify the best opportunities
Acceptance Criteria:
- Compare TVL, volume, and fees across pools
- Show fee tier differences (0.05%, 0.30%, 1%)
- Highlight volume/TVL efficiency
- Display historical performance
US-5: Monitor Pool Health (Medium)
As a liquidity provider I want to monitor my LP positions for risks So that I can react to adverse conditions
Acceptance Criteria:
- Alert on significant TVL changes
- Warn on imbalanced token ratios
- Flag low liquidity or volume drop
- Track price divergence from oracles
Functional Requirements
REQ-1: Pool Data Aggregation
- Fetch pool data from DEX subgraphs (Uniswap, Curve, Balancer)
- Support multiple chains (Ethereum, Arbitrum, Polygon, BSC)
- Normalize pool metrics across protocols
- Cache data with appropriate TTL
REQ-2: Impermanent Loss Calculator
- Calculate IL from entry price to current price
- Support various price change scenarios
- Compare IL across different fee tiers
- Estimate breakeven time based on volume
REQ-3: Fee Analysis
- Calculate realized fees from swap volume
- Project fee income over time periods
- Factor in protocol fee splits
- Include reward token APY if applicable
REQ-4: Pool Health Metrics
- Track TVL trends over time
- Monitor token ratio imbalances
- Detect unusual volume patterns
- Compare against oracle prices
REQ-5: Output Formats
- Table format for terminal display
- JSON for programmatic use
- Detailed pool analysis report
- Comparison tables
API Integrations
- The Graph: Uniswap V2/V3, Curve, Balancer subgraphs
- DeFiLlama: Pool TVL and volume data
- CoinGecko: Token prices for IL calculations
- Dune Analytics: Advanced on-chain queries (optional)
Non-Goals
- Automated LP position management
- Trading execution or swaps
- Gas estimation for deposits/withdrawals
- Portfolio tracking across multiple wallets
Success Metrics
- Skill activates on pool analysis phrases
- IL calculations match established formulas
- Pool data accuracy vs. protocol frontends
- Response time < 10 seconds for standard queries
Technical Constraints
- Python 3.8+ with requests library
- No private key or wallet connection
- Subgraph rate limits (100 req/min)
- Data freshness: blocks may be ~15s behind
Risk Assessment
| Risk | Probability | Impact | Mitigation |
|---|---|---|---|
| Subgraph data delays | Medium | Medium | Multiple data sources, timestamp warnings |
| IL formula errors | Low | High | Test against known calculators |
| Pool not indexed | Medium | Low | Fallback to on-chain RPC calls |
| Price oracle divergence | Medium | Medium | Compare multiple price sources |
Examples
Example 1: Analyze Specific Pool
python pool_analyzer.py --pool 0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640 --chain ethereumExample 2: Calculate IL Scenario
python pool_analyzer.py --il-calc --entry-price 2000 --current-price 3000 --token-pair ETH/USDCExample 3: Compare Similar Pools
python pool_analyzer.py --compare --pair ETH/USDC --protocols uniswap-v3,curve,balancerVersion History
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0.0 | 2026-01-15 | Jeremy Longshore | Initial PRD |
Error Handling Reference
API Errors
Subgraph Query Timeout
Error: requests.exceptions.Timeout: Request timed out after 15sCause: The Graph subgraph is slow or unavailable. Solution:
- Script automatically falls back to DeFiLlama data
- Wait and retry if fresh subgraph data is needed
- Check The Graph status page for outages
Subgraph Rate Limit
Error: 429 Too Many RequestsCause: Too many subgraph queries in short period. Solution:
- The Graph allows ~100 queries/minute on free tier
- Cache is used by default to reduce queries
- Wait 60 seconds before retrying
DeFiLlama API Error
Error: Failed to fetch from DeFiLlamaCause: DeFiLlama API unavailable. Solution:
- Falls back to cached data if available
- Uses mock data for testing if needed
- Retry after a few minutes
Pool Errors
Pool Not Found
Error: Pool not found on ethereumCause: Pool address doesn't exist or wrong chain. Solution:
- Verify pool address is correct
- Check you're using the right chain flag (--chain)
- Address must be lowercase for subgraphs
Invalid Pool Address
Error: Invalid pool address formatCause: Address format is incorrect. Solution:
- Ensure address starts with 0x
- Address should be 42 characters (0x + 40 hex chars)
- Use lowercase for consistency
Token Pair Not Found
Warning: No pools found for ETH/BTCCause: No pools exist for this pair on the selected protocol. Solution:
- Try different protocol (--protocol)
- Check token symbols are correct
- Some pairs only exist on certain chains
Calculation Errors
Invalid Price Values
Error: Entry price must be greater than 0Cause: Invalid price input for IL calculation. Solution:
- Entry and current prices must be positive numbers
- Don't include currency symbols ($)
- Use decimal notation (2000, not 2,000)
IL Calculation Error
Error: Cannot calculate IL for negative price ratioCause: Current price is 0 or negative. Solution:
- Verify price inputs are correct
- Current price cannot be 0
Output Errors
File Write Permission Denied
PermissionError: [Errno 13] Permission denied: 'output.json'Cause: Cannot write to output file location. Solution:
- Check file/directory permissions
- Try different output path
- Use stdout (no --output flag)
Invalid Output Format
Error: Invalid format 'xml'Cause: Unsupported output format. Solution: Use supported formats:
table(default)jsoncsv
Cache Errors
Cache Read Error
Warning: Could not read cache fileCause: Cache file corrupted or permissions issue. Solution:
- Delete cache:
rm ~/.lp_analyzer_cache.json - Script will fetch fresh data
Stale Cache Warning
Using cached data (10 min old)Cause: Cache TTL exceeded but API unavailable. Solution:
- Not critical - data is still usable
- Use
--no-cacheto force refresh when API is back
Import Errors
Missing requests Library
Warning: requests library not availableCause: Python requests package not installed. Solution:
pip install requestsModule Not Found
ModuleNotFoundError: No module named 'pool_fetcher'Cause: Running script from wrong directory. Solution:
# Run from scripts directory or use full path
python /path/to/scripts/pool_analyzer.py --helpRecovery Commands
Reset Cache
rm ~/.lp_analyzer_cache.jsonTest Subgraph Connectivity
curl -X POST https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3 \
-H "Content-Type: application/json" \
-d '{"query": "{ pools(first: 1) { id } }"}'Verbose Mode for Debugging
python pool_analyzer.py --verbose --pair ETH/USDCTest with Known Pool
# Uniswap V3 ETH/USDC 0.05% pool
python pool_analyzer.py --pool 0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640 --verbose--- [Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)
Usage Examples
Basic Pool Analysis
Analyze Pool by Address
python pool_analyzer.py --pool 0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640
# Output:
# ==============================================================================
# LIQUIDITY POOL ANALYZER 2025-01-14 15:30 UTC
# ==============================================================================
#
# POOL: USDC-WETH (uniswap-v3 - 0.05%)
# ------------------------------------------------------------------------------
# Chain: Ethereum
# TVL: $500.00M
# 24h Volume: $125.00M
# Fee Tier: 0.05%
#
# FEE METRICS
# ------------------------------------------------------------------------------
# 24h Fees: $62,500.00
# Fee APR: 4.56%
# Volume/TVL: 0.2500Search by Token Pair
python pool_analyzer.py --pair ETH/USDC --protocol uniswap-v3
# Shows all ETH/USDC pools on Uniswap V3Filter by Chain
python pool_analyzer.py --pair ETH/USDC --chain arbitrum
# Shows ETH/USDC pools on Arbitrum onlyImpermanent Loss Calculation
Calculate IL for Price Change
python pool_analyzer.py --il-calc --entry-price 2000 --current-price 3000
# Output:
# ==============================================================================
# IMPERMANENT LOSS CALCULATION 2025-01-14 15:30 UTC
# ==============================================================================
#
# ------------------------------------------------------------------------------
# Entry Price: $2,000.00
# Current Price: $3,000.00
# Price Change: +50.0%
#
# IL (%): 2.02%
# ==============================================================================IL with Position Value
python pool_analyzer.py --il-calc --entry-price 2000 --current-price 3000 --position 10000
# Output includes:
# IL (USD): $202.00
# Value if HODL: $12,500.00
# Value in LP: $12,247.45IL Scenarios Table
python pool_analyzer.py --il-scenarios
# Output:
# ==============================================================================
# IMPERMANENT LOSS SCENARIOS
# ==============================================================================
#
# ------------------------------------------------------------------------------
# Price Change Price Ratio IL
# ------------------------------------------------------------------------------
# -90% 0.10x 42.54%
# -75% 0.25x 20.00%
# -50% 0.50x 5.72%
# -25% 0.75x 1.03%
# 0% 1.00x 0.00%
# +25% 1.25x 0.62%
# +50% 1.50x 2.02%
# +100% 2.00x 5.72%
# +200% 3.00x 13.40%
# +400% 5.00x 25.46%
# ------------------------------------------------------------------------------
# ==============================================================================Pool Comparison
Compare Pools Across Protocols
python pool_analyzer.py --compare --pair ETH/USDC --protocols uniswap-v3,curve,balancer
# Output:
# ==============================================================================
# POOL COMPARISON
# ------------------------------------------------------------------------------
# Protocol Pair Chain TVL Fee APR
# ------------------------------------------------------------------------------
# uniswap-v3 USDC-WETH Ethereum $500M 4.56%
# curve USDC-ETH Ethereum $50M 3.21%
# balancer WETH-USDC Ethereum $25M 2.89%
# ------------------------------------------------------------------------------
# Total: 3 pools
# ==============================================================================Compare Fee Tiers
python pool_analyzer.py --pair ETH/USDC --protocol uniswap-v3 --fee-tiers 0.01,0.05,0.30
# Shows pools at different fee tiers for comparisonPosition Analysis
Project Returns for Position
python pool_analyzer.py --pool 0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640 --position 10000
# Adds position metrics:
# Position Share: 0.0020%
# Daily Fees: $1.25
# Weekly Fees: $8.75
# Monthly Fees: $37.50
# Annual Fees: $456.25Filtering Options
Filter by Minimum TVL
python pool_analyzer.py --protocol uniswap-v3 --min-tvl 10000000
# Shows only pools with TVL >= $10MLimit Results
python pool_analyzer.py --protocol uniswap-v3 --top 5
# Shows only top 5 poolsOutput Formats
JSON Output
python pool_analyzer.py --pool 0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640 --format json
# Output:
# {
# "timestamp": "2025-01-14 15:30 UTC",
# "data": [
# {
# "pool": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
# "project": "uniswap-v3",
# "chain": "Ethereum",
# "symbol": "USDC-WETH",
# "metrics": {
# "tvl": 500000000,
# "volume_24h": 125000000,
# "fee_apr": 4.56,
# "health_score": 90
# }
# }
# ]
# }CSV Output
python pool_analyzer.py --protocol uniswap-v3 --top 10 --format csv --output pools.csv
# Creates CSV file with headers:
# Protocol,Symbol,Chain,Pool Address,TVL (USD),Volume 24h,Fee Tier (%),Fee APR (%),Volume/TVL,Health ScoreSave to File
python pool_analyzer.py --pool 0x88e6... --format json --output analysis.json
python pool_analyzer.py --pair ETH/USDC --format csv --output comparison.csvVerbose Mode
Debug Output
python pool_analyzer.py --pair ETH/USDC --verbose
# Shows:
# Fetching pool data...
# Querying Uniswap V3 subgraph...
# Falling back to DeFiLlama...
# Found 15 pools for ETH/USDC
# Calculated metrics for USDC-WETH
# ...Real-World Workflows
Find Best Pool for LP Position
# 1. Search pools for your pair
python pool_analyzer.py --pair ETH/USDC --protocol uniswap-v3 --min-tvl 1000000
# 2. Analyze specific pool
python pool_analyzer.py --pool 0x88e6... --detailed
# 3. Calculate IL risk at target price
python pool_analyzer.py --il-calc --entry-price 2000 --current-price 2500 --position 10000Monitor Position Profitability
# Check current pool metrics
python pool_analyzer.py --pool 0x88e6... --position 10000
# Calculate IL if price drops 20%
python pool_analyzer.py --il-calc --entry-price 2000 --current-price 1600 --position 10000Research DeFi Yields
# Compare pools across protocols
python pool_analyzer.py --pair ETH/USDC --compare --protocols uniswap-v3,curve,balancer --format csv --output research.csv
# View IL scenarios
python pool_analyzer.py --il-scenarios --format json --output il_table.jsonIntegration Examples
Pipe to jq
python pool_analyzer.py --protocol uniswap-v3 --top 5 --format json | jq '.data[].metrics.fee_apr'Use in Shell Scripts
#!/bin/bash
# Get top APR pool
BEST_POOL=$(python pool_analyzer.py --pair ETH/USDC --format json | jq -r '.data[0].pool')
echo "Best pool: $BEST_POOL"--- [Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)
Analyzing Liquidity Pools - Implementation Reference
Detailed Output Formats
Pool Analysis Summary
==============================================================================
LIQUIDITY POOL ANALYZER 2026-01-15 15:30 UTC
==============================================================================
POOL: USDC/WETH (Uniswap V3 - 0.05%)
------------------------------------------------------------------------------
Chain: Ethereum
TVL: $500.5M
24h Volume: $125.3M
Fee Tier: 0.05%
FEE METRICS
------------------------------------------------------------------------------
24h Fees: $62,650
Fee APR: 4.57%
Volume/TVL: 0.25
TOKEN COMPOSITION
------------------------------------------------------------------------------
USDC: $252.1M (50.4%)
WETH: $248.4M (49.6%)
Current Price: $2,450/ETH
==============================================================================Impermanent Loss Report
IMPERMANENT LOSS CALCULATION
------------------------------------------------------------------------------
Entry Price: $2,000/ETH
Current Price: $3,000/ETH
Price Change: +50%
IL (%) -5.72%
IL ($1000 LP): -$57.20
Value if HODL: $1,250.00
Value in LP: $1,192.80
BREAKEVEN ANALYSIS (0.05% fee tier)
------------------------------------------------------------------------------
Daily Fees: $0.63 (at $500M TVL, $125M vol)
Days to Break: 91 days
Monthly Fees: $18.90
==============================================================================Configuration
Settings in ${CLAUDE_SKILL_DIR}/config/settings.yaml:
- Default chain: Primary chain to query
- Cache TTL: How long to cache subgraph data
- Subgraph endpoints: URLs for each protocol
- Fee tier defaults: Common fee tier options
Advanced Comparisons
Cross-Protocol Comparison
When comparing pools across protocols, the analyzer normalizes:
- Fee structures (fixed vs dynamic)
- TVL denominated in USD
- Volume-weighted fee APR
- Gas costs per trade
Fee Tier Analysis
For Uniswap V3, compare fee tiers for the same pair:
- 0.01% - Stablecoin pairs
- 0.05% - Major pairs (ETH/USDC)
- 0.30% - Standard pairs
- 1.00% - Exotic pairs
Data Sources
- The Graph: https://thegraph.com/ - Subgraph queries
- Uniswap Info: https://info.uniswap.org/ - Pool explorer
- DeFiLlama: https://defillama.com/ - TVL data
- Impermanent Loss Calculator: https://dailydefi.org/tools/impermanent-loss-calculator/
--- [Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)
#!/usr/bin/env python3
"""
Pool Formatters
Formats pool analysis output.
Author: Jeremy Longshore <jeremy@intentsolutions.io>
Version: 2.0.0
License: MIT
"""
import json
import csv
import io
from datetime import datetime
from typing import Dict, Any, List
class PoolFormatter:
"""Formats pool analysis for display."""
def __init__(self):
"""Initialize formatter."""
self.timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M UTC")
def format(
self,
data: Any,
format_type: str = "table",
detailed: bool = False
) -> str:
"""Format data for output.
Args:
data: Pool or list of pools
format_type: Output format (table, json, csv)
detailed: Show detailed breakdown
Returns:
Formatted string
"""
if format_type == "json":
return self._format_json(data)
elif format_type == "csv":
return self._format_csv(data)
else:
if isinstance(data, list):
return self._format_pool_list(data, detailed)
else:
return self._format_single_pool(data, detailed)
def _format_single_pool(
self,
pool: Dict[str, Any],
detailed: bool = False
) -> str:
"""Format a single pool analysis.
Args:
pool: Pool data
detailed: Show detailed breakdown
Returns:
Formatted string
"""
lines = []
metrics = pool.get("metrics", {})
# Header
lines.append("=" * 78)
lines.append(f" LIQUIDITY POOL ANALYZER{' ' * 31}{self.timestamp}")
lines.append("=" * 78)
lines.append("")
# Pool info
protocol = pool.get("project", "Unknown")
symbol = pool.get("symbol", "?")
chain = pool.get("chain", "?")
fee_tier = metrics.get("fee_tier_pct", 0)
lines.append(f" POOL: {symbol} ({protocol} - {fee_tier}%)")
lines.append("-" * 78)
lines.append(f" Chain: {chain}")
# Format TVL
tvl = metrics.get("tvl", 0)
lines.append(f" TVL: {self._format_usd(tvl)}")
# Format Volume
volume = metrics.get("volume_24h", 0)
lines.append(f" 24h Volume: {self._format_usd(volume)}")
lines.append(f" Fee Tier: {fee_tier}%")
lines.append("")
# Fee metrics
lines.append(" FEE METRICS")
lines.append("-" * 78)
lines.append(f" 24h Fees: {self._format_usd(metrics.get('daily_fees', 0))}")
lines.append(f" Fee APR: {metrics.get('fee_apr', 0):.2f}%")
lines.append(f" Volume/TVL: {metrics.get('volume_tvl_ratio', 0):.4f}")
lines.append("")
# Token composition (if available)
token0 = pool.get("token0", {})
token1 = pool.get("token1", {})
if token0 or token1:
lines.append(" TOKEN COMPOSITION")
lines.append("-" * 78)
if token0.get("symbol"):
lines.append(f" {token0.get('symbol', 'Token0')}: (token details)")
if token1.get("symbol"):
lines.append(f" {token1.get('symbol', 'Token1')}: (token details)")
lines.append("")
# Health indicators
health_score = metrics.get("health_score", 0)
health_level = metrics.get("health_level", "unknown")
lines.append(" HEALTH INDICATORS")
lines.append("-" * 78)
lines.append(f" Health Score: {health_score}/100 ({health_level.title()})")
lines.append(f" Efficiency: {metrics.get('capital_efficiency', 'N/A').replace('_', ' ').title()}")
warnings = metrics.get("warnings", [])
if warnings:
lines.append("")
lines.append(" Warnings:")
for w in warnings:
lines.append(f" - {w}")
lines.append("=" * 78)
return "\n".join(lines)
def _format_pool_list(
self,
pools: List[Dict[str, Any]],
detailed: bool = False
) -> str:
"""Format a list of pools as table.
Args:
pools: List of pools
detailed: Show detailed breakdown
Returns:
Formatted string
"""
lines = []
# Header
lines.append("=" * 78)
lines.append(f" LIQUIDITY POOL ANALYZER{' ' * 31}{self.timestamp}")
lines.append("=" * 78)
lines.append("")
if not pools:
lines.append(" No pools found matching criteria.")
lines.append("=" * 78)
return "\n".join(lines)
# Table header
lines.append(" POOL COMPARISON")
lines.append("-" * 78)
header = f" {'Protocol':<15} {'Pair':<15} {'Chain':<12} {'TVL':>10} {'Fee APR':>10}"
lines.append(header)
lines.append("-" * 78)
# Table rows
for pool in pools:
metrics = pool.get("metrics", {})
protocol = (pool.get("project") or "?")[:14]
symbol = (pool.get("symbol") or "?")[:14]
chain = (pool.get("chain") or "?")[:11]
tvl = metrics.get("tvl", pool.get("tvlUsd", 0))
fee_apr = metrics.get("fee_apr", 0)
tvl_str = self._format_usd_short(tvl)
row = f" {protocol:<15} {symbol:<15} {chain:<12} {tvl_str:>10} {fee_apr:>9.2f}%"
lines.append(row)
lines.append("-" * 78)
lines.append(f" Total: {len(pools)} pools")
lines.append("=" * 78)
return "\n".join(lines)
def format_il_report(
self,
il_data: Dict[str, Any],
pool: Dict[str, Any] = None
) -> str:
"""Format impermanent loss report.
Args:
il_data: IL calculation results
pool: Optional pool data for context
Returns:
Formatted string
"""
lines = []
lines.append("=" * 78)
lines.append(f" IMPERMANENT LOSS CALCULATION{' ' * 26}{self.timestamp}")
lines.append("=" * 78)
lines.append("")
lines.append("-" * 78)
lines.append(f" Entry Price: ${il_data.get('entry_price', 0):,.2f}")
lines.append(f" Current Price: ${il_data.get('current_price', 0):,.2f}")
lines.append(f" Price Change: {il_data.get('price_change_pct', 0):+.1f}%")
lines.append("")
lines.append(f" IL (%): {il_data.get('il_pct', 0):.2f}%")
if 'position_value' in il_data:
lines.append(f" IL (USD): ${il_data.get('il_usd', 0):,.2f}")
lines.append("")
lines.append(f" Value if HODL: ${il_data.get('value_if_held', 0):,.2f}")
lines.append(f" Value in LP: ${il_data.get('value_in_lp', 0):,.2f}")
# Breakeven analysis if available
if pool and pool.get("metrics"):
lines.append("")
lines.append(" BREAKEVEN ANALYSIS")
lines.append("-" * 78)
metrics = pool["metrics"]
lines.append(f" Fee Tier: {metrics.get('fee_tier_pct', 0)}%")
lines.append(f" Daily Fee APR: {metrics.get('fee_apr', 0)/365:.4f}%")
# Calculate breakeven days
il_pct = abs(il_data.get('il_pct', 0))
daily_fee_pct = metrics.get('fee_apr', 0) / 365
if daily_fee_pct > 0:
days = il_pct / daily_fee_pct
lines.append(f" Days to Break: {days:.0f} days")
lines.append("=" * 78)
return "\n".join(lines)
def format_il_scenarios(
self,
scenarios: List[Dict[str, float]]
) -> str:
"""Format IL scenarios table.
Args:
scenarios: List of IL scenarios
Returns:
Formatted string
"""
lines = []
lines.append("=" * 78)
lines.append(" IMPERMANENT LOSS SCENARIOS")
lines.append("=" * 78)
lines.append("")
lines.append("-" * 78)
header = f" {'Price Change':>15} {'Price Ratio':>15} {'IL':>15}"
lines.append(header)
lines.append("-" * 78)
for s in scenarios:
pct = s.get("price_change_pct", 0)
ratio = s.get("price_ratio", 1)
il = s.get("il_pct", 0)
row = f" {pct:>+14.0f}% {ratio:>14.2f}x {il:>14.2f}%"
lines.append(row)
lines.append("-" * 78)
lines.append("=" * 78)
return "\n".join(lines)
def _format_json(self, data: Any) -> str:
"""Format as JSON.
Args:
data: Data to format
Returns:
JSON string
"""
output = {
"timestamp": self.timestamp,
"data": data if isinstance(data, list) else [data],
}
return json.dumps(output, indent=2, default=str)
def _format_csv(self, data: Any) -> str:
"""Format as CSV.
Args:
data: Data to format
Returns:
CSV string
"""
if not isinstance(data, list):
data = [data]
output = io.StringIO()
writer = csv.writer(output)
# Header
writer.writerow([
"Protocol", "Symbol", "Chain", "Pool Address",
"TVL (USD)", "Volume 24h", "Fee Tier (%)", "Fee APR (%)",
"Volume/TVL", "Health Score"
])
for pool in data:
metrics = pool.get("metrics", {})
writer.writerow([
pool.get("project", ""),
pool.get("symbol", ""),
pool.get("chain", ""),
pool.get("pool", ""),
metrics.get("tvl", pool.get("tvlUsd", 0)),
metrics.get("volume_24h", pool.get("volumeUsd", 0)),
metrics.get("fee_tier_pct", 0),
metrics.get("fee_apr", 0),
metrics.get("volume_tvl_ratio", 0),
metrics.get("health_score", 0),
])
return output.getvalue()
def _format_usd(self, value: float) -> str:
"""Format USD value with appropriate suffix.
Args:
value: USD amount
Returns:
Formatted string
"""
if value >= 1e9:
return f"${value/1e9:.2f}B"
elif value >= 1e6:
return f"${value/1e6:.2f}M"
elif value >= 1e3:
return f"${value/1e3:.1f}K"
else:
return f"${value:,.2f}"
def _format_usd_short(self, value: float) -> str:
"""Format USD value compactly.
Args:
value: USD amount
Returns:
Formatted string
"""
if value >= 1e9:
return f"${value/1e9:.1f}B"
elif value >= 1e6:
return f"${value/1e6:.0f}M"
elif value >= 1e3:
return f"${value/1e3:.0f}K"
else:
return f"${value:.0f}"
def main():
"""CLI entry point for testing."""
formatter = PoolFormatter()
# Test pool data
pool = {
"pool": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
"project": "uniswap-v3",
"chain": "Ethereum",
"symbol": "USDC-WETH",
"token0": {"symbol": "USDC"},
"token1": {"symbol": "WETH"},
"metrics": {
"tvl": 500_000_000,
"volume_24h": 125_000_000,
"fee_tier_pct": 0.05,
"daily_fees": 62500,
"fee_apr": 4.56,
"volume_tvl_ratio": 0.25,
"capital_efficiency": "high",
"health_score": 90,
"health_level": "healthy",
"warnings": [],
}
}
print(formatter.format(pool, "table"))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Impermanent Loss Calculator
Calculates impermanent loss for liquidity positions.
Author: Jeremy Longshore <jeremy@intentsolutions.io>
Version: 2.0.0
License: MIT
"""
import math
from typing import Dict, Any, List, Tuple
class ILCalculator:
"""Calculates impermanent loss for LP positions."""
def __init__(self, verbose: bool = False):
"""Initialize calculator.
Args:
verbose: Enable verbose output
"""
self.verbose = verbose
def calculate_il(self, price_ratio: float) -> float:
"""Calculate impermanent loss for a price change.
The formula: IL = 2 * sqrt(r) / (1 + r) - 1
where r = new_price / original_price
Args:
price_ratio: Ratio of new price to original price
Returns:
IL as decimal (negative means loss)
"""
if price_ratio <= 0:
return 0.0
il = (2 * math.sqrt(price_ratio) / (1 + price_ratio)) - 1
if self.verbose:
print(f" Price ratio {price_ratio:.2f}x → IL {il*100:.2f}%")
return il
def calculate_il_from_prices(
self,
entry_price: float,
current_price: float
) -> Dict[str, float]:
"""Calculate IL with detailed breakdown.
Args:
entry_price: Price at LP entry
current_price: Current price
Returns:
Dictionary with IL details
"""
price_ratio = current_price / entry_price
price_change_pct = (price_ratio - 1) * 100
il_decimal = self.calculate_il(price_ratio)
il_pct = il_decimal * 100
return {
"entry_price": entry_price,
"current_price": current_price,
"price_ratio": round(price_ratio, 4),
"price_change_pct": round(price_change_pct, 2),
"il_decimal": round(il_decimal, 6),
"il_pct": round(il_pct, 2),
}
def calculate_position_il(
self,
entry_price: float,
current_price: float,
position_value: float
) -> Dict[str, float]:
"""Calculate IL for a specific position size.
Args:
entry_price: Price at LP entry
current_price: Current price
position_value: Initial position value in USD
Returns:
Dictionary with position IL details
"""
il_info = self.calculate_il_from_prices(entry_price, current_price)
price_ratio = current_price / entry_price
# Value if just held 50/50 split
# Assuming entry was 50% token0 (USD) and 50% token1 (volatile)
initial_token1_amount = (position_value / 2) / entry_price
value_if_held = (position_value / 2) + (initial_token1_amount * current_price)
# Value in LP (affected by IL)
value_in_lp = value_if_held * (1 + il_info["il_decimal"])
il_usd = value_in_lp - value_if_held
return {
**il_info,
"position_value": position_value,
"value_if_held": round(value_if_held, 2),
"value_in_lp": round(value_in_lp, 2),
"il_usd": round(il_usd, 2),
"gain_vs_hold_pct": round((value_in_lp / value_if_held - 1) * 100, 2),
}
def calculate_breakeven(
self,
il_pct: float,
fee_tier: float,
tvl: float,
daily_volume: float
) -> Dict[str, float]:
"""Calculate days to break even from fees.
Args:
il_pct: Impermanent loss percentage (negative)
fee_tier: Pool fee tier (e.g., 0.003 for 0.3%)
tvl: Pool TVL in USD
daily_volume: Daily trading volume in USD
Returns:
Dictionary with breakeven analysis
"""
if tvl <= 0 or daily_volume <= 0 or fee_tier <= 0:
return {
"days_to_breakeven": float("inf"),
"daily_fee_pct": 0,
"monthly_fee_pct": 0,
}
# Daily fee percentage earned by LPs
daily_fees_usd = daily_volume * fee_tier
daily_fee_pct = (daily_fees_usd / tvl) * 100
# Days to recover IL
il_abs = abs(il_pct)
if daily_fee_pct > 0:
days_to_breakeven = il_abs / daily_fee_pct
else:
days_to_breakeven = float("inf")
return {
"il_pct": il_pct,
"fee_tier_pct": fee_tier * 100,
"daily_fee_pct": round(daily_fee_pct, 4),
"monthly_fee_pct": round(daily_fee_pct * 30, 2),
"annual_fee_pct": round(daily_fee_pct * 365, 2),
"days_to_breakeven": round(days_to_breakeven, 1) if days_to_breakeven != float("inf") else None,
}
def generate_il_scenarios(
self,
price_changes: List[float] = None
) -> List[Dict[str, float]]:
"""Generate IL for various price change scenarios.
Args:
price_changes: List of price change percentages
Returns:
List of IL scenarios
"""
if price_changes is None:
price_changes = [-75, -50, -25, -10, 10, 25, 50, 100, 200, 300, 400]
scenarios = []
for pct_change in price_changes:
price_ratio = 1 + (pct_change / 100)
if price_ratio > 0:
il = self.calculate_il(price_ratio)
scenarios.append({
"price_change_pct": pct_change,
"price_ratio": round(price_ratio, 2),
"il_pct": round(il * 100, 2),
})
return scenarios
def compare_strategies(
self,
entry_price: float,
current_price: float,
position_value: float,
fee_earned_pct: float
) -> Dict[str, Any]:
"""Compare LP strategy vs HODL.
Args:
entry_price: Price at LP entry
current_price: Current price
position_value: Initial position value
fee_earned_pct: Total fees earned as percentage
Returns:
Strategy comparison
"""
position_info = self.calculate_position_il(
entry_price, current_price, position_value
)
# Value with fees added
fees_earned = position_value * (fee_earned_pct / 100)
lp_value_with_fees = position_info["value_in_lp"] + fees_earned
# Net result
net_gain_usd = lp_value_with_fees - position_info["value_if_held"]
net_gain_pct = (net_gain_usd / position_info["value_if_held"]) * 100
return {
**position_info,
"fees_earned_pct": fee_earned_pct,
"fees_earned_usd": round(fees_earned, 2),
"lp_value_with_fees": round(lp_value_with_fees, 2),
"net_gain_usd": round(net_gain_usd, 2),
"net_gain_pct": round(net_gain_pct, 2),
"better_strategy": "LP" if net_gain_usd > 0 else "HODL",
}
def main():
"""CLI entry point for testing."""
calc = ILCalculator(verbose=True)
print("Impermanent Loss Scenarios:")
print("-" * 50)
scenarios = calc.generate_il_scenarios()
for s in scenarios:
print(f" {s['price_change_pct']:+4d}% price change → {s['il_pct']:+6.2f}% IL")
print("\nPosition Analysis:")
print("-" * 50)
position = calc.calculate_position_il(
entry_price=2000,
current_price=3000,
position_value=10000
)
print(f" Entry: ${position['entry_price']}")
print(f" Current: ${position['current_price']}")
print(f" Price Change: {position['price_change_pct']:+.2f}%")
print(f" IL: {position['il_pct']:.2f}%")
print(f" Value if HODL: ${position['value_if_held']:,.2f}")
print(f" Value in LP: ${position['value_in_lp']:,.2f}")
print(f" IL Loss: ${position['il_usd']:,.2f}")
print("\nBreakeven Analysis (0.3% fee tier):")
print("-" * 50)
breakeven = calc.calculate_breakeven(
il_pct=position['il_pct'],
fee_tier=0.003,
tvl=500_000_000,
daily_volume=100_000_000
)
print(f" Daily Fee APR: {breakeven['daily_fee_pct']:.4f}%")
print(f" Annual Fee APR: {breakeven['annual_fee_pct']:.2f}%")
if breakeven['days_to_breakeven']:
print(f" Days to Breakeven: {breakeven['days_to_breakeven']:.0f}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Liquidity Pool Analyzer - Main CLI
Analyze DEX liquidity pools for TVL, volume, fees, and impermanent loss.
Author: Jeremy Longshore <jeremy@intentsolutions.io>
Version: 2.0.0
License: MIT
"""
import argparse
import sys
from pathlib import Path
# Add scripts directory to path for imports
sys.path.insert(0, str(Path(__file__).parent))
from pool_fetcher import PoolFetcher
from il_calculator import ILCalculator
from pool_metrics import PoolMetrics
from formatters import PoolFormatter
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Analyze DEX liquidity pools",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s --pool 0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640
%(prog)s --pair ETH/USDC --protocol uniswap-v3
%(prog)s --il-calc --entry-price 2000 --current-price 3000
%(prog)s --compare --pair ETH/USDC --protocols uniswap-v3,curve
"""
)
# Pool identification
parser.add_argument(
"--pool", "-p",
help="Pool address to analyze"
)
parser.add_argument(
"--pair",
help="Token pair to search (e.g., ETH/USDC)"
)
parser.add_argument(
"--protocol",
help="Protocol filter (uniswap-v3, curve, balancer, etc.)"
)
parser.add_argument(
"--chain", "-c",
default="ethereum",
help="Blockchain (ethereum, arbitrum, polygon, etc.)"
)
# IL calculation
parser.add_argument(
"--il-calc",
action="store_true",
help="Calculate impermanent loss"
)
parser.add_argument(
"--entry-price",
type=float,
help="Entry price for IL calculation"
)
parser.add_argument(
"--current-price",
type=float,
help="Current price for IL calculation"
)
parser.add_argument(
"--il-scenarios",
action="store_true",
help="Show IL for various price scenarios"
)
# Position analysis
parser.add_argument(
"--position",
type=float,
help="Position size in USD for projections"
)
# Comparison
parser.add_argument(
"--compare",
action="store_true",
help="Compare pools"
)
parser.add_argument(
"--protocols",
help="Protocols to compare, comma-separated"
)
parser.add_argument(
"--fee-tiers",
help="Fee tiers to compare, comma-separated"
)
# Filters
parser.add_argument(
"--min-tvl",
type=float,
default=0,
help="Minimum TVL in USD"
)
parser.add_argument(
"--top", "-t",
type=int,
default=10,
help="Number of results to show"
)
# Output options
parser.add_argument(
"--format", "-f",
choices=["table", "json", "csv"],
default="table",
help="Output format"
)
parser.add_argument(
"--output", "-o",
help="Output file"
)
parser.add_argument(
"--detailed",
action="store_true",
help="Show detailed analysis"
)
# Other
parser.add_argument(
"--no-cache",
action="store_true",
help="Bypass cache"
)
parser.add_argument(
"--verbose", "-v",
action="store_true",
help="Verbose output"
)
parser.add_argument(
"--version",
action="version",
version="%(prog)s 2.0.0"
)
args = parser.parse_args()
try:
# Initialize components
fetcher = PoolFetcher(use_cache=not args.no_cache, verbose=args.verbose)
il_calc = ILCalculator(verbose=args.verbose)
metrics_calc = PoolMetrics(verbose=args.verbose)
formatter = PoolFormatter()
# Handle IL calculation mode
if args.il_calc and args.entry_price and args.current_price:
return handle_il_calculation(args, il_calc, formatter)
# Handle IL scenarios mode
if args.il_scenarios:
return handle_il_scenarios(il_calc, formatter, args)
# Fetch pools
if args.verbose:
print("Fetching pool data...")
pools = []
if args.pool:
# Specific pool by address
pool = fetcher.fetch_pool_by_address(
args.pool,
chain=args.chain,
protocol=args.protocol or "uniswap-v3"
)
if pool:
pools = [pool]
elif args.pair:
# Search by token pair
tokens = args.pair.upper().replace("/", "-").split("-")
if len(tokens) >= 2:
pools = fetcher.fetch_pools_by_pair(
tokens[0], tokens[1],
chain=args.chain if args.chain != "ethereum" else None,
protocol=args.protocol
)
elif args.protocol:
# All pools for a protocol
pools = fetcher.fetch_pools_by_protocol(
args.protocol,
chain=args.chain if args.chain != "ethereum" else None,
min_tvl=args.min_tvl
)
else:
print("Please specify --pool, --pair, or --protocol", file=sys.stderr)
sys.exit(1)
if not pools:
print("No pools found matching criteria.", file=sys.stderr)
sys.exit(0)
# Apply filters
if args.min_tvl > 0:
pools = [p for p in pools if (p.get("tvlUsd") or 0) >= args.min_tvl]
# Calculate metrics
for pool in pools:
metrics_calc.calculate_metrics(pool)
# Limit results
pools = pools[:args.top]
if not pools:
print("No pools match criteria after filtering.", file=sys.stderr)
sys.exit(0)
# Handle position projection
if args.position and len(pools) == 1:
position_metrics = metrics_calc.calculate_position_metrics(pools[0], args.position)
pools[0]["position"] = position_metrics
# Format output
if len(pools) == 1 and not args.compare:
output = formatter.format(pools[0], args.format, args.detailed)
else:
output = formatter.format(pools, args.format, args.detailed)
# Output
if args.output:
with open(args.output, "w") as f:
f.write(output)
print(f"Results saved to {args.output}")
else:
print(output)
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
if args.verbose:
import traceback
traceback.print_exc()
sys.exit(1)
def handle_il_calculation(args, il_calc, formatter):
"""Handle IL calculation mode.
Args:
args: CLI arguments
il_calc: IL calculator instance
formatter: Output formatter
Returns:
Exit code
"""
position_value = args.position or 1000
il_data = il_calc.calculate_position_il(
entry_price=args.entry_price,
current_price=args.current_price,
position_value=position_value
)
if args.format == "json":
output = formatter._format_json(il_data)
else:
output = formatter.format_il_report(il_data)
if args.output:
with open(args.output, "w") as f:
f.write(output)
print(f"Results saved to {args.output}")
else:
print(output)
return 0
def handle_il_scenarios(il_calc, formatter, args):
"""Handle IL scenarios mode.
Args:
il_calc: IL calculator instance
formatter: Output formatter
args: CLI arguments
Returns:
Exit code
"""
scenarios = il_calc.generate_il_scenarios()
if args.format == "json":
output = formatter._format_json(scenarios)
else:
output = formatter.format_il_scenarios(scenarios)
if args.output:
with open(args.output, "w") as f:
f.write(output)
print(f"Results saved to {args.output}")
else:
print(output)
return 0
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Pool Fetcher
Fetches liquidity pool data from subgraphs and APIs.
Author: Jeremy Longshore <jeremy@intentsolutions.io>
Version: 2.0.0
License: MIT
"""
import json
import time
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, List, Optional
try:
import requests
except ImportError:
requests = None
class PoolFetcher:
"""Fetches pool data from DEX subgraphs and APIs."""
# DeFiLlama endpoints
DEFILLAMA_POOLS_URL = "https://yields.llama.fi/pools"
# Subgraph endpoints
SUBGRAPH_ENDPOINTS = {
"uniswap-v3-ethereum": "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3",
"uniswap-v3-arbitrum": "https://api.thegraph.com/subgraphs/name/ianlapham/uniswap-v3-arbitrum",
"uniswap-v3-polygon": "https://api.thegraph.com/subgraphs/name/ianlapham/uniswap-v3-polygon",
"uniswap-v2-ethereum": "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2",
}
CACHE_FILE = Path.home() / ".lp_analyzer_cache.json"
CACHE_TTL = 300 # 5 minutes
def __init__(self, use_cache: bool = True, verbose: bool = False):
"""Initialize fetcher.
Args:
use_cache: Whether to use cached data
verbose: Enable verbose output
"""
self.use_cache = use_cache
self.verbose = verbose
self._cache = {}
def fetch_pool_by_address(
self,
address: str,
chain: str = "ethereum",
protocol: str = "uniswap-v3"
) -> Optional[Dict[str, Any]]:
"""Fetch pool data by contract address.
Args:
address: Pool contract address
chain: Blockchain (ethereum, arbitrum, etc.)
protocol: DEX protocol
Returns:
Pool data dictionary or None
"""
cache_key = f"pool_{address}_{chain}"
# Check cache
if self.use_cache:
cached = self._load_cache(cache_key)
if cached:
if self.verbose:
print(f" Using cached data for {address[:10]}...")
return cached
# Try subgraph first
subgraph_key = f"{protocol}-{chain}"
if subgraph_key in self.SUBGRAPH_ENDPOINTS:
pool = self._fetch_from_subgraph(address, subgraph_key)
if pool:
self._save_cache(cache_key, pool)
return pool
# Fallback to DeFiLlama
pool = self._fetch_from_defillama(address)
if pool:
self._save_cache(cache_key, pool)
return pool
# Return mock data as last resort
if self.verbose:
print(f" Pool not found, using mock data")
return self._get_mock_pool(address)
def fetch_pools_by_pair(
self,
token0: str,
token1: str,
chain: str = None,
protocol: str = None
) -> List[Dict[str, Any]]:
"""Fetch pools for a token pair.
Args:
token0: First token symbol
token1: Second token symbol
chain: Optional chain filter
protocol: Optional protocol filter
Returns:
List of matching pools
"""
# Fetch from DeFiLlama (has all pools)
all_pools = self._fetch_all_pools()
# Normalize search terms
search_tokens = {token0.upper(), token1.upper()}
# Filter matching pools
matches = []
for pool in all_pools:
symbol = pool.get("symbol", "").upper()
# Check if both tokens are in the symbol
if all(t in symbol for t in search_tokens):
if chain and pool.get("chain", "").lower() != chain.lower():
continue
if protocol and protocol.lower() not in pool.get("project", "").lower():
continue
matches.append(pool)
# Sort by TVL
matches.sort(key=lambda x: -(x.get("tvlUsd") or 0))
if self.verbose:
print(f" Found {len(matches)} pools for {token0}/{token1}")
return matches
def fetch_pools_by_protocol(
self,
protocol: str,
chain: str = None,
min_tvl: float = 0
) -> List[Dict[str, Any]]:
"""Fetch all pools for a protocol.
Args:
protocol: Protocol name (uniswap-v3, curve, etc.)
chain: Optional chain filter
min_tvl: Minimum TVL filter
Returns:
List of pools
"""
all_pools = self._fetch_all_pools()
matches = []
for pool in all_pools:
if protocol.lower() not in pool.get("project", "").lower():
continue
if chain and pool.get("chain", "").lower() != chain.lower():
continue
if (pool.get("tvlUsd") or 0) < min_tvl:
continue
matches.append(pool)
matches.sort(key=lambda x: -(x.get("tvlUsd") or 0))
return matches
def _fetch_all_pools(self) -> List[Dict[str, Any]]:
"""Fetch all pools from DeFiLlama.
Returns:
List of all pools
"""
cache_key = "all_pools"
if self.use_cache:
cached = self._load_cache(cache_key)
if cached:
return cached
if requests is None:
return self._get_mock_pools()
try:
if self.verbose:
print(f" Fetching from DeFiLlama...")
response = requests.get(
self.DEFILLAMA_POOLS_URL,
timeout=30,
headers={"Accept": "application/json"}
)
response.raise_for_status()
data = response.json()
pools = data.get("data", [])
if self.use_cache and pools:
self._save_cache(cache_key, pools)
return pools
except Exception as e:
if self.verbose:
print(f" API error: {e}")
return self._get_mock_pools()
def _fetch_from_subgraph(
self,
address: str,
subgraph_key: str
) -> Optional[Dict[str, Any]]:
"""Fetch pool from The Graph subgraph.
Args:
address: Pool address
subgraph_key: Subgraph identifier
Returns:
Pool data or None
"""
if requests is None:
return None
endpoint = self.SUBGRAPH_ENDPOINTS.get(subgraph_key)
if not endpoint:
return None
query = """
query Pool($id: ID!) {
pool(id: $id) {
id
token0 { symbol, decimals, name }
token1 { symbol, decimals, name }
feeTier
liquidity
sqrtPrice
tick
totalValueLockedUSD
volumeUSD
feesUSD
txCount
createdAtTimestamp
}
}
"""
try:
response = requests.post(
endpoint,
json={"query": query, "variables": {"id": address.lower()}},
timeout=15
)
response.raise_for_status()
data = response.json()
pool_data = data.get("data", {}).get("pool")
if pool_data:
return self._normalize_subgraph_pool(pool_data, subgraph_key)
except Exception as e:
if self.verbose:
print(f" Subgraph error: {e}")
return None
def _fetch_from_defillama(self, address: str) -> Optional[Dict[str, Any]]:
"""Fetch pool from DeFiLlama by address.
Args:
address: Pool address
Returns:
Pool data or None
"""
pools = self._fetch_all_pools()
address_lower = address.lower()
for pool in pools:
pool_id = pool.get("pool", "")
if address_lower in pool_id.lower():
return pool
return None
def _normalize_subgraph_pool(
self,
pool: Dict[str, Any],
subgraph_key: str
) -> Dict[str, Any]:
"""Normalize subgraph pool data to standard format.
Args:
pool: Raw subgraph pool data
subgraph_key: Source subgraph
Returns:
Normalized pool data
"""
# Extract chain and protocol from subgraph key
parts = subgraph_key.split("-")
protocol = "-".join(parts[:-1])
chain = parts[-1]
fee_tier = int(pool.get("feeTier", 0))
fee_pct = fee_tier / 10000 if fee_tier else 0.003
return {
"pool": pool.get("id"),
"project": protocol,
"chain": chain.capitalize(),
"symbol": f"{pool['token0']['symbol']}-{pool['token1']['symbol']}",
"token0": pool["token0"],
"token1": pool["token1"],
"tvlUsd": float(pool.get("totalValueLockedUSD", 0)),
"volumeUsd": float(pool.get("volumeUSD", 0)),
"feesUsd": float(pool.get("feesUSD", 0)),
"feeTier": fee_pct,
"txCount": int(pool.get("txCount", 0)),
"createdAt": pool.get("createdAtTimestamp"),
"source": "subgraph",
}
def _load_cache(self, key: str) -> Optional[Any]:
"""Load data from cache.
Args:
key: Cache key
Returns:
Cached data or None
"""
if not self.CACHE_FILE.exists():
return None
try:
with open(self.CACHE_FILE, "r") as f:
cache = json.load(f)
entry = cache.get(key)
if not entry:
return None
# Check TTL
cached_time = entry.get("timestamp", 0)
if time.time() - cached_time > self.CACHE_TTL:
return None
return entry.get("data")
except (json.JSONDecodeError, IOError):
return None
def _save_cache(self, key: str, data: Any) -> None:
"""Save data to cache.
Args:
key: Cache key
data: Data to cache
"""
try:
cache = {}
if self.CACHE_FILE.exists():
with open(self.CACHE_FILE, "r") as f:
cache = json.load(f)
cache[key] = {
"timestamp": time.time(),
"data": data
}
with open(self.CACHE_FILE, "w") as f:
json.dump(cache, f)
except IOError:
pass
def _get_mock_pool(self, address: str) -> Dict[str, Any]:
"""Get mock pool data for testing.
Args:
address: Pool address
Returns:
Mock pool data
"""
return {
"pool": address,
"project": "uniswap-v3",
"chain": "Ethereum",
"symbol": "ETH-USDC",
"token0": {"symbol": "USDC", "decimals": 6},
"token1": {"symbol": "WETH", "decimals": 18},
"tvlUsd": 500_000_000,
"volumeUsd": 100_000_000,
"feeTier": 0.0005,
"feesUsd": 50_000,
"source": "mock",
}
def _get_mock_pools(self) -> List[Dict[str, Any]]:
"""Get mock pools for testing.
Returns:
List of mock pools
"""
return [
{
"pool": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
"project": "uniswap-v3",
"chain": "Ethereum",
"symbol": "USDC-WETH",
"tvlUsd": 500_000_000,
"volumeUsd": 125_000_000,
"apy": 4.5,
"feeTier": 0.0005,
},
{
"pool": "0x4e68ccd3e89f51c3074ca5072bbac773960dfa36",
"project": "uniswap-v3",
"chain": "Ethereum",
"symbol": "WETH-USDT",
"tvlUsd": 150_000_000,
"volumeUsd": 45_000_000,
"apy": 8.2,
"feeTier": 0.003,
},
{
"pool": "curve-3pool",
"project": "curve-dex",
"chain": "Ethereum",
"symbol": "DAI-USDC-USDT",
"tvlUsd": 890_000_000,
"volumeUsd": 50_000_000,
"apy": 2.1,
"feeTier": 0.0004,
},
]
def main():
"""CLI entry point for testing."""
fetcher = PoolFetcher(verbose=True)
print("Fetching ETH/USDC pools:")
pools = fetcher.fetch_pools_by_pair("ETH", "USDC")
print(f"\nFound {len(pools)} pools:")
for pool in pools[:5]:
tvl = pool.get("tvlUsd", 0)
if tvl >= 1e9:
tvl_str = f"${tvl/1e9:.1f}B"
elif tvl >= 1e6:
tvl_str = f"${tvl/1e6:.0f}M"
else:
tvl_str = f"${tvl:,.0f}"
print(f" {pool.get('project')}: {pool.get('symbol')} on {pool.get('chain')} - TVL: {tvl_str}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Pool Metrics Calculator
Calculates derived metrics for liquidity pools.
Author: Jeremy Longshore <jeremy@intentsolutions.io>
Version: 2.0.0
License: MIT
"""
from typing import Dict, Any, List, Optional
class PoolMetrics:
"""Calculates metrics for liquidity pools."""
def __init__(self, verbose: bool = False):
"""Initialize metrics calculator.
Args:
verbose: Enable verbose output
"""
self.verbose = verbose
def calculate_metrics(self, pool: Dict[str, Any]) -> Dict[str, Any]:
"""Calculate all metrics for a pool.
Args:
pool: Pool data dictionary
Returns:
Pool with metrics added
"""
# Basic metrics
pool["metrics"] = {}
# TVL metrics
self._calculate_tvl_metrics(pool)
# Fee metrics
self._calculate_fee_metrics(pool)
# Efficiency metrics
self._calculate_efficiency_metrics(pool)
# Health indicators
self._calculate_health_indicators(pool)
if self.verbose:
print(f" Calculated metrics for {pool.get('symbol')}")
return pool
def _calculate_tvl_metrics(self, pool: Dict[str, Any]) -> None:
"""Calculate TVL-related metrics.
Args:
pool: Pool data (modified in place)
"""
tvl = pool.get("tvlUsd", 0)
volume_24h = pool.get("volumeUsd", 0) or pool.get("volume24h", 0)
# TVL category
if tvl >= 100_000_000:
tvl_category = "large"
elif tvl >= 10_000_000:
tvl_category = "medium"
elif tvl >= 1_000_000:
tvl_category = "small"
else:
tvl_category = "micro"
pool["metrics"]["tvl"] = tvl
pool["metrics"]["tvl_category"] = tvl_category
pool["metrics"]["volume_24h"] = volume_24h
def _calculate_fee_metrics(self, pool: Dict[str, Any]) -> None:
"""Calculate fee-related metrics.
Args:
pool: Pool data (modified in place)
"""
tvl = pool.get("tvlUsd", 0)
volume_24h = pool.get("volumeUsd", 0) or pool.get("volume24h", 0)
fee_tier = pool.get("feeTier", 0.003) # Default 0.3%
# Ensure fee_tier is decimal (not percentage)
if fee_tier > 1:
fee_tier = fee_tier / 10000 # Convert from basis points
# Calculate fees
if volume_24h > 0:
daily_fees = volume_24h * fee_tier
else:
daily_fees = 0
# Calculate APR
if tvl > 0 and daily_fees > 0:
fee_apr = (daily_fees / tvl) * 365 * 100
else:
fee_apr = 0
pool["metrics"]["fee_tier"] = fee_tier
pool["metrics"]["fee_tier_pct"] = round(fee_tier * 100, 4)
pool["metrics"]["daily_fees"] = round(daily_fees, 2)
pool["metrics"]["weekly_fees"] = round(daily_fees * 7, 2)
pool["metrics"]["monthly_fees"] = round(daily_fees * 30, 2)
pool["metrics"]["annual_fees"] = round(daily_fees * 365, 2)
pool["metrics"]["fee_apr"] = round(fee_apr, 2)
def _calculate_efficiency_metrics(self, pool: Dict[str, Any]) -> None:
"""Calculate efficiency metrics.
Args:
pool: Pool data (modified in place)
"""
tvl = pool.get("tvlUsd", 0)
volume_24h = pool.get("volumeUsd", 0) or pool.get("volume24h", 0)
# Volume/TVL ratio (turnover)
if tvl > 0:
volume_tvl_ratio = volume_24h / tvl
else:
volume_tvl_ratio = 0
# Capital efficiency rating
if volume_tvl_ratio >= 0.5:
efficiency = "very_high"
elif volume_tvl_ratio >= 0.1:
efficiency = "high"
elif volume_tvl_ratio >= 0.05:
efficiency = "medium"
elif volume_tvl_ratio >= 0.01:
efficiency = "low"
else:
efficiency = "very_low"
pool["metrics"]["volume_tvl_ratio"] = round(volume_tvl_ratio, 4)
pool["metrics"]["capital_efficiency"] = efficiency
def _calculate_health_indicators(self, pool: Dict[str, Any]) -> None:
"""Calculate pool health indicators.
Args:
pool: Pool data (modified in place)
"""
tvl = pool.get("tvlUsd", 0)
volume_24h = pool.get("volumeUsd", 0) or pool.get("volume24h", 0)
fee_apr = pool["metrics"].get("fee_apr", 0)
warnings = []
health_score = 100
# TVL check
if tvl < 100_000:
warnings.append("Very low TVL - high slippage risk")
health_score -= 30
elif tvl < 1_000_000:
warnings.append("Low TVL - moderate slippage risk")
health_score -= 15
# Volume check
if volume_24h < 10_000:
warnings.append("Very low volume - illiquid")
health_score -= 25
elif volume_24h < 100_000:
warnings.append("Low volume - may be illiquid")
health_score -= 10
# Fee APR check
if fee_apr < 1:
warnings.append("Low fee income - may not offset IL")
health_score -= 15
elif fee_apr > 100:
warnings.append("Unusually high APR - verify data")
health_score -= 10
# Volume/TVL ratio check
vol_tvl = pool["metrics"].get("volume_tvl_ratio", 0)
if vol_tvl < 0.01:
warnings.append("Very low capital utilization")
health_score -= 10
# Determine health level
if health_score >= 80:
health_level = "healthy"
elif health_score >= 60:
health_level = "moderate"
elif health_score >= 40:
health_level = "poor"
else:
health_level = "risky"
pool["metrics"]["health_score"] = max(0, health_score)
pool["metrics"]["health_level"] = health_level
pool["metrics"]["warnings"] = warnings
def calculate_position_metrics(
self,
pool: Dict[str, Any],
position_value: float
) -> Dict[str, Any]:
"""Calculate metrics for a specific position size.
Args:
pool: Pool data with metrics
position_value: Position size in USD
Returns:
Position-specific metrics
"""
metrics = pool.get("metrics", {})
tvl = metrics.get("tvl", 0)
fee_apr = metrics.get("fee_apr", 0)
# Position share of pool
if tvl > 0:
position_share = (position_value / tvl) * 100
else:
position_share = 0
# Projected fees
daily_fees_pool = metrics.get("daily_fees", 0)
if tvl > 0:
position_daily_fees = daily_fees_pool * (position_value / tvl)
else:
position_daily_fees = 0
return {
"position_value": position_value,
"position_share_pct": round(position_share, 4),
"daily_fees": round(position_daily_fees, 2),
"weekly_fees": round(position_daily_fees * 7, 2),
"monthly_fees": round(position_daily_fees * 30, 2),
"annual_fees": round(position_daily_fees * 365, 2),
"fee_apr": fee_apr,
}
def compare_pools(
self,
pools: List[Dict[str, Any]]
) -> Dict[str, Any]:
"""Compare metrics across multiple pools.
Args:
pools: List of pools with metrics
Returns:
Comparison summary
"""
if not pools:
return {}
# Find best in each category
max_tvl = max(pools, key=lambda p: p.get("metrics", {}).get("tvl", 0))
max_apr = max(pools, key=lambda p: p.get("metrics", {}).get("fee_apr", 0))
max_efficiency = max(pools, key=lambda p: p.get("metrics", {}).get("volume_tvl_ratio", 0))
healthiest = max(pools, key=lambda p: p.get("metrics", {}).get("health_score", 0))
return {
"pool_count": len(pools),
"highest_tvl": {
"pool": max_tvl.get("symbol"),
"tvl": max_tvl.get("metrics", {}).get("tvl"),
},
"highest_apr": {
"pool": max_apr.get("symbol"),
"apr": max_apr.get("metrics", {}).get("fee_apr"),
},
"most_efficient": {
"pool": max_efficiency.get("symbol"),
"ratio": max_efficiency.get("metrics", {}).get("volume_tvl_ratio"),
},
"healthiest": {
"pool": healthiest.get("symbol"),
"score": healthiest.get("metrics", {}).get("health_score"),
},
}
def main():
"""CLI entry point for testing."""
calculator = PoolMetrics(verbose=True)
# Test pool
pool = {
"pool": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
"project": "uniswap-v3",
"chain": "Ethereum",
"symbol": "USDC-WETH",
"tvlUsd": 500_000_000,
"volumeUsd": 125_000_000,
"feeTier": 0.0005,
}
calculator.calculate_metrics(pool)
print("\nPool Metrics:")
print("-" * 50)
metrics = pool["metrics"]
print(f" TVL: ${metrics['tvl']:,.0f} ({metrics['tvl_category']})")
print(f" Volume/24h: ${metrics['volume_24h']:,.0f}")
print(f" Fee Tier: {metrics['fee_tier_pct']}%")
print(f" Daily Fees: ${metrics['daily_fees']:,.2f}")
print(f" Fee APR: {metrics['fee_apr']:.2f}%")
print(f" Volume/TVL: {metrics['volume_tvl_ratio']:.4f}")
print(f" Capital Efficiency: {metrics['capital_efficiency']}")
print(f" Health Score: {metrics['health_score']}/100 ({metrics['health_level']})")
if metrics["warnings"]:
print(f" Warnings:")
for w in metrics["warnings"]:
print(f" - {w}")
print("\nPosition Projection ($10,000):")
position = calculator.calculate_position_metrics(pool, 10000)
print(f" Share of Pool: {position['position_share_pct']:.4f}%")
print(f" Daily Fees: ${position['daily_fees']:.2f}")
print(f" Monthly Fees: ${position['monthly_fees']:.2f}")
print(f" Annual Fees: ${position['annual_fees']:.2f}")
if __name__ == "__main__":
main()