Skip to content

SimulatePriceMove

SimulatePriceMove is the read-only, stateless primitive that projects a Uniswap V2 or V3 LP position’s value at a hypothetical price change. Paper projection — no settlement-swap impact, no fee modeling. Use it to answer “if ETH drops 30% from here, what’s my position worth?”

Sibling primitives handle the other AMM families:

ProtocolRequired call shape
Uniswap V2SimulatePriceMove().apply(lp, price_change_pct, position_size_lp)
Uniswap V3SimulatePriceMove().apply(lp, price_change_pct, position_size_lp, lwr_tick, upr_tick)
Balancer❌ Use SimulateBalancerPriceMove
Stableswap❌ Use SimulateStableswapPriceMove
ParameterTypeDescription
lpUniswapExchangeV2 or V3 LP exchange.
price_change_pctfloatFractional price change (-0.30 = 30% drop). Bounded by > -1.0.
position_size_lpfloatPosition size in LP-token units. The result is scale-invariant in this size — use any positive value.
lwr_tick, upr_tickint (V3 only)Position’s tick range.

The projection: starting from the pool’s current α (the price ratio current_price / entry_price), project to α' = α · (1 + price_change_pct). The IL formula evaluates at α’:

IL(α)=2α1+α1\mathrm{IL}(\alpha') = \frac{2\sqrt{\alpha'}}{1 + \alpha'} - 1

Composed via UniswapImpLoss’s calc_iloss — the same helper that powers AnalyzePosition, evaluated at a counterfactual α.

Numeraire: token0 (matches AnalyzePosition).

V3 range factor. For V3, calc_iloss(α, r) accepts the range r = P_upper / P_lower and scales accordingly. Outside the range the position behaves like a 100% allocation to one side; tight ranges amplify IL inside the range.

fee_projection = None in v1. The primitive does not model fee accrual at the projected price — it’s a pure-IL projection. Callers wanting “IL minus projected fees” combine this primitive’s output with their own fee-rate assumption (or use FindBreakEvenTime for the reverse question).

Scale-invariance in position_size_lp. All result fields scale linearly with position_size_lp, so the IL fraction is identical regardless of the input — pass any positive value.

from defipy import SimulatePriceMove
from defipy.twin import MockProvider, StateTwinBuilder
provider = MockProvider()
builder = StateTwinBuilder()
lp_v2 = builder.build(provider.snapshot("eth_dai_v2"))
# What happens to a 10,000 LP-share position if ETH drops 30%?
result = SimulatePriceMove().apply(
lp_v2,
price_change_pct = -0.30,
position_size_lp = 10000.0,
)
print(f"new_price_ratio: {result.new_price_ratio:.6f}")
print(f"new_value: {result.new_value:.4f}")
print(f"il_at_new_price: {result.il_at_new_price:.6f}")
print(f"value_change_pct: {result.value_change_pct:.6f}")
print(f"fee_projection: {result.fee_projection}")
new_price_ratio: 0.700000 new_value: 2390.4572 il_at_new_price: -0.015694 value_change_pct: 0.195229 fee_projection: None

A 30% ETH drop produces ~1.57% IL on the V2 position — the curve is symmetric in α, so a 30% rise would give the same IL fraction.

  • Composes UniswapImpLoss’s calc_iloss evaluated at the projected α.
  • Composed into by CompareProtocols for the IL-at-shock leg of cross-protocol comparison.