Building an Impermanent Loss Calculator and Position Monitor with AI

Build a real-time LP position tracker that calculates impermanent loss, fee income, and net P&L across Uniswap V3, Curve, and Balancer — with AI-generated rebalancing alerts.

Your Uniswap V3 ETH/USDC position shows +12% APY in fees. It doesn't show the -18% impermanent loss quietly eating your principal.

You’re not farming yield; you’re subsidizing traders with your principal while chasing green numbers on a dashboard. The brutal truth of concentrated liquidity is that your advertised APY is a conditional promise, valid only if the price stays politely within your chosen tick range. When it bolts, you’re left holding an imbalanced bag, earning zero fees, and watching impermanent loss solidify into a very permanent regret.

This guide is for the LP who’s tired of guessing. We’re building a live impermanent loss calculator and position monitor that uses AI not as a buzzword, but as a pattern-recognition engine on your Dune Analytics data. You’ll move from reactive dashboard-watching to proactive strategy management with Python, The Graph, and a logic-driven alert system.

Understanding the Real Math: Your Principal is a Variable

Forget the textbook 2 * sqrt(priceRatio) / (1 + priceRatio) - 1 formula for a second. In Uniswap V3, it’s more nuanced. Your capital efficiency is a double-edged sword. That 4x efficiency versus v2 in the same price range means your exposure—and thus your potential loss—is also magnified.

Let’s ground this with the hard data: the average impermanent loss for an ETH/USDC 0.3% pool is about 1.2% over a 30-day period with a 40% price swing (Dune Analytics). That seems manageable until you realize that fee income must first cover this loss before you see real profit. If ETH moons 80% while your narrow range is stuck at +20%, your IL isn’t just theoretical; it’s the opportunity cost of not having just held ETH.

The calculation must account for:

  1. Initial Deposit Values: How much of token X and Y you deposited.
  2. Current Portfolio Value: The value of your LP position at current prices.
  3. Hold Portfolio Value: What that initial deposit would be worth if you’d just held the tokens in your wallet.
  4. Accrued Fees: The unclaimed fee income in the position. This is your only hedge against IL.

Impermanent Loss (%) = (Current Portfolio Value - Hold Portfolio Value) / Hold Portfolio Value * 100

A negative result means IL has outpaced fees. This is the core metric our system will track in real-time.

Setting Up Your Data Pipeline: Dune Analytics + AI as a Co-pilot

You can’t manage what you can’t measure. While The Graph (via its subgraphs) will give us precise, real-time blockchain state, Dune Analytics is where we go for aggregated wisdom and historical context. It’s the difference between knowing your position’s current fee amount and understanding the 30-day fee trend for your entire pool.

First, get your API keys:

  1. The Graph: Go to the Uniswap V3 Subgraph and use the provided endpoint for queries.
  2. Dune Analytics: Create an account and generate an API key from your profile.

Now, open VS Code (Ctrl+\`` to open the terminal). We’ll set up a Python environment and use GitHub Copilot (or Continue.dev) to accelerate the boilerplate. With Copilot enabled, start typing a comment like # Fetch my LP position from The Graphand hitTab—it’ll often write the robust requests` call for you.


import os
import requests
import pandas as pd
from dotenv import load_dotenv

load_dotenv()  # Load GRAPH_API_KEY, DUNE_API_KEY from .env

GRAPH_ENDPOINT = "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3"
DUNE_HEADERS = {"x-dune-api-key": os.getenv("DUNE_API_KEY")}

def fetch_position_from_graph(position_id):
    """Fetches real-time data for a specific Uniswap V3 LP position."""
    query = """
    query($id: String!) {
      position(id: $id) {
        id
        owner
        pool {
          id
          token0 { symbol, decimals }
          token1 { symbol, decimals }
          feeTier
        }
        liquidity
        tickLower { tickIdx }
        tickUpper { tickIdx }
        depositedToken0
        depositedToken1
        withdrawnToken0
        withdrawnToken1
        collectedFeesToken0
        collectedFeesToken1
        transaction { timestamp }
      }
    }
    """
    variables = {"id": position_id.lower()}
    response = requests.post(GRAPH_ENDPOINT, json={'query': query, 'variables': variables})
    data = response.json()
    return data['data']['position']

Real Error & Fix: You query The Graph and get "Cannot return null for non-nullable field Pool.token0". This means your position_id is malformed or the position doesn't exist. Fix: The position ID must be in the format {pool_id}#{tick_lower}#{tick_upper}. Use a blockchain explorer to find the correct ID.

Building the Impermanent Loss Engine in Python

With raw position data in hand, we translate it into financial reality. This function calculates the current value of your LP share and the dreaded "what if I just held" scenario.

# il_calculator.py
from decimal import Decimal, getcontext
import math

getcontext().prec = 30  # High precision for financial math

def calculate_impermanent_loss(position_data, current_price, token0_decimals=18, token1_decimals=6):
    """
    Calculates impermanent loss for a Uniswap V3 position.
    position_data: Dict from The Graph query.
    current_price: Current price of token0 in terms of token1 (e.g., ETH/USDC price).
    """
    # 1. Extract initial deposits (adjust for decimals)
    init_token0 = Decimal(position_data['depositedToken0']) / (10 ** token0_decimals)
    init_token1 = Decimal(position_data['depositedToken1']) / (10 ** token1_decimals)

    # 2. Calculate "HOLD" portfolio value at current price
    hold_value = (init_token0 * Decimal(current_price)) + init_token1

    # 3. Calculate CURRENT position value (simplified - assumes price in range)
    # In production, you'd use the liquidity math to get precise token amounts.
    # This is a simplified estimator for the article.
    tick_lower = float(position_data['tickLower']['tickIdx'])
    tick_upper = float(position_data['tickUpper']['tickIdx'])
    price_lower = 1.0001 ** tick_lower
    price_upper = 1.0001 ** tick_upper

    if current_price <= price_lower:
        # Price below range: position is 100% token1
        current_value = init_token1
    elif current_price >= price_upper:
        # Price above range: position is 100% token0
        current_value = init_token0 * Decimal(current_price)
    else:
        # Price in range: use a proportional estimate
        # Note: For a real calculator, implement the exact Uniswap V3 liquidity formulas.
        proportion_in_token0 = (Decimal(math.sqrt(price_upper)) - Decimal(math.sqrt(current_price))) / (Decimal(math.sqrt(price_upper)) - Decimal(math.sqrt(price_lower)))
        current_token0 = init_token0 * proportion_in_token0
        current_token1 = init_token1 * (1 - proportion_in_token0)
        current_value = (current_token0 * Decimal(current_price)) + current_token1

    # 4. Add accrued fees (from position data)
    fees_token0 = Decimal(position_data['collectedFeesToken0']) / (10 ** token0_decimals)
    fees_token1 = Decimal(position_data['collectedFeesToken1']) / (10 ** token1_decimals)
    fees_value = (fees_token0 * Decimal(current_price)) + fees_token1
    total_current_value = current_value + fees_value

    # 5. Impermanent Loss Calculation
    if hold_value > 0:
        il_percent = ((total_current_value - hold_value) / hold_value) * 100
    else:
        il_percent = Decimal(0)

    return {
        "hold_portfolio_value": float(hold_value),
        "current_position_value": float(total_current_value),
        "impermanent_loss_percent": float(il_percent),
        "fee_income_value": float(fees_value)
    }

# Example usage
position = fetch_position_from_graph("0x...your_position_id...")
eth_price = 3500.0  # Fetch from an oracle like Uniswap pool itself
result = calculate_impermanent_loss(position, eth_price)
print(f"IL: {result['impermanent_loss_percent']:.2f}%, Fees Earned: ${result['fee_income_value']:.2f}")

Connecting to Live Data and Building the Monitor

A one-off calculation is a snapshot; we need a live feed. We’ll create a monitor that polls The Graph and a price oracle, then logs the P&L over time. We’ll use Dune Analytics to cross-reference our position’s performance against the pool’s broader metrics.

# position_monitor.py
import schedule
import time
from datetime import datetime
import sqlite3

def monitor_job(position_id, pool_address):
    """Scheduled job to fetch, calculate, and store position health."""
    print(f"\n--- {datetime.utcnow().isoformat()} ---")

    # 1. Get live position state
    position = fetch_position_from_graph(position_id)
    # 2. Get current price from the pool itself (more reliable than external API)
    pool_data = fetch_pool_state(pool_address)  # Implement using The Graph pool query
    current_price = float(pool_data['token0Price'])

    # 3. Run Calculator
    metrics = calculate_impermanent_loss(position, current_price)

    # 4. Fetch pool benchmark from Dune (example: total daily fees)
    dune_query_id = 1234567  # Your pre-saved Dune query for this pool's daily fees
    pool_daily_fees = fetch_dune_data(dune_query_id)
    my_share_of_fees = estimate_fee_share(position, pool_daily_fees)

    # 5. Store & Alert
    store_metrics(position_id, datetime.utcnow(), metrics)
    check_alerts(metrics, my_share_of_fees)

    print(f"Price: ${current_price:.2f} | IL: {metrics['impermanent_loss_percent']:.2f}% | Net PnL: ${metrics['current_position_value'] - metrics['hold_portfolio_value']:.2f}")

# Schedule to run every 10 minutes
schedule.every(10).minutes.do(monitor_job, position_id="0x...", pool_address="0x...")

print("Starting Uniswap V3 Position Monitor...")
while True:
    schedule.run_pending()
    time.sleep(60)

Real Error & Fix: Your script fails with "Slippage tolerance too low causes failed txs" when you try to add a simulated rebalance trade. Fix: When using the calculated data to trigger rebalances via a smart contract, always set slippage tolerance to 0.5–1% for volatile pairs in your transaction parameters. Don't use the default 0.1%.

AI-Powered Alert Logic: From Data to Decisions

This is where we move from a dashboard to a strategist. We’ll define rules, and use an LLM (via an extension like Continue.dev) to analyze trend summaries and suggest non-obvious actions.

Don’t let the term "AI" intimidate you. We’re simply using a language model to interpret structured data trends it sees in your logs—something it’s excellent at.

# ai_alert_engine.py
import json
from continue_client import ContinueClient  # Hypothetical client for Continue.dev extension

def generate_ai_insight(metrics_history, pool_context):
    """
    Sends a weekly summary to an LLM for analysis and insight generation.
    """
    client = ContinueClient()

    prompt = f"""
    As a DeFi strategy analyst, review this liquidity position's weekly performance and context.
    Provide a concise, actionable insight (max 3 sentences). Focus on fee income vs impermanent loss trends and the provided market context.

    PERFORMANCE HISTORY (last 7 days):
    {json.dumps(metrics_history[-10:], indent=2)}  # Last 10 data points

    POOL & MARKET CONTEXT:
    - Pool Type: {pool_context['type']} (e.g., ETH/USDC 0.3%)
- 30-Day Avg Pool Fee APY: {pool_context['fee_apy']}%
- Current Price vs Position Range: {pool_context['price_vs_range']}
- Gas Cost to Rebalance: ${pool_context['rebalance_gas_cost']} (on Arbitrum)

    ANALYSIS REQUEST:
    1. Is fee income consistently covering impermanent loss?
    2. Based on the price vs range, is the position at risk of falling out-of-range?
    3. Suggest one specific action: HOLD, REBALANCE (widen/narrow range), or EXIT.
    """

    insight = client.complete(prompt)
    return insight

# Example rule-based alert
def check_alerts(metrics, fee_share):
    alerts = []
    # Rule 1: Out-of-range alert
    if metrics['current_position_value'] == metrics['hold_portfolio_value']:  # Simplified check for out-of-range
        alerts.append("CRITICAL: Position is out-of-range and earning 0 fees.")
        # Fix: Monitor price and be prepared to rebalance or withdraw when price exits range.

    # Rule 2: Fee income not covering IL
    if metrics['fee_income_value'] < abs(metrics['current_position_value'] - metrics['hold_portfolio_value']) and metrics['impermanent_loss_percent'] < 0:
        alerts.append("WARNING: Fee income is not covering impermanent loss.")

    # Rule 3: IL exceeds threshold (e.g., >5%)
    if metrics['impermanent_loss_percent'] < -5:
        alerts.append(f"WARNING: Impermanent loss ({metrics['impermanent_loss_percent']:.2f}%) exceeds 5% threshold.")

    for alert in alerts:
        print(f"[ALERT] {alert}")
        # In production, send to Discord/Slack/Telegram

The Fee vs. IL Showdown: Where Your Strategy Actually Lives

Your choice of pool is a direct bet on volatility versus fee yield. Let’s benchmark using real data. A narrow range on a volatile pair might promise high APY, but a single swing can wipe out weeks of fees. A wide range on a stable pair earns less but sleeps better.

Pool Type (Protocol)Typical Fee APYIL Risk (30d, 40% Swing)Capital EfficiencyBest For
ETH/USDC Narrow (Uniswap v3)12-25%+ (volatile)High (>5%)4x (Concentrated)Active managers, strong price views
ETH/USDC Wide (Uniswap v2/v3)4-8%Moderate (~1.2%)1xPassive holders, bullish long-term
Stablecoin (Curve)4-12% (fees + CRV)Very Low (<0.1%)High via veCRVYield on stablecoins, low risk
Correlated Assets (Balancer)5-15%Low to ModerateConfigurableDiversified portfolio exposure

The Takeaway: If you’re not actively monitoring and rebalancing, the "high APY" of a concentrated Uniswap V3 position is a mirage. For most, the Top Curve pools yielding 4–12% APY from fees + CRV emissions with minimal IL are a more rational choice. Convex Finance, controlling 51% of all veCRV, is a testament to where the smart, passive yield has consolidated.

When to Fold 'Em: Encoding Your Exit Strategy

The hardest decision is to exit. Our AI-assisted logic here doesn't trade for you; it flags when your predefined exit conditions are met, backed by data.

  1. The Fee Drought: Your position has been out-of-range for >48 hours, earning nothing. The cost of gas to rebalance exceeds a week of potential fees. Exit Signal: Trigger.
  2. The Underperformance: Your net APY (fees - IL) consistently lags a benchmark (e.g., a Yearn vault averaging 5–18% net APY**). Why take active risk for passive returns? Exit Signal: Trigger.
  3. The Rule of 20: Impermanent loss exceeds fee income when price moves >20%. If a single, sustained move breaches this, your strategy is fundamentally broken. Mitigation: Use narrow tick ranges only in stable pairs where >20% moves are rare.

The exit logic in your monitor might look like this:

if (days_out_of_range > 2 and rebalance_cost > projected_weekly_fees) or (net_apy_30d < yearn_benchmark_apy) or (abs(price_change) > 0.20 and il_percent > fee_yield_percent):
    send_alert("EXIT SIGNAL GENERATED", priority="HIGH")
    # Provide one-click link to withdraw on Uniswap interface

Next Steps: From Monitor to Autonomous Agent

You’ve now built a system that transforms raw blockchain data into a clear, actionable picture of your liquidity position. You’re no longer farming blind.

Where to take this next?

  1. Automate the Boring Stuff: Connect the alert system to a secure transaction relayer (using a multisig or a time-locked contract) to execute rebalances or exits when you confirm via a signed message.
  2. Multi-Protocol Dashboard: Integrate data from Pendle Finance for yield tokenization, Aave v3 for collateral health on leveraged positions, and Beefy Finance for auto-compounding vaults. Use DefiLlama’s API as your yield benchmark.
  3. Backtest Your Ranges: Use the historical price data from Dune to simulate what your IL and fees would have been over the last year with different tick ranges. Let the AI suggest an optimal range based on historical volatility.
  4. Move to a Subgraph: For production, host your own subgraph for faster, customized queries of your positions.

The goal isn't to eliminate impermanent loss—that's baked into the AMM model. The goal is to ensure your fee income is always winning the fight against it. Stop letting your principal bleed out silently in a pool. Equip yourself with data, automate the surveillance, and make your liquidity work for you, not against you.