Skip to content

SimulateStableswapPriceMove

SimulateStableswapPriceMove is the read-only, stateless primitive that projects a 2-asset stableswap LP position’s value at a hypothetical depeg. Sibling to SimulatePriceMove (V2/V3) and SimulateBalancerPriceMove.

High A makes small price deviations costly — use small moves (a 5% move is meaningful, a 30% move usually isn’t reachable for typical A).

ProtocolRequired call shape
Uniswap V2❌ Use SimulatePriceMove
Uniswap V3❌ Use SimulatePriceMove
Balancer❌ Use SimulateBalancerPriceMove
StableswapSimulateStableswapPriceMove().apply(lp, price_change_pct, lp_init_amt)
ParameterTypeDescription
lpStableswapExchange2-asset stableswap pool. N>2 raises ValueError (propagated from StableswapImpLoss).
price_change_pctfloatFractional price change. Use small values (typically |x| ≤ 0.10); larger shocks are often unreachable for typical A.
lp_init_amtfloatPool shares held by the position.

Three steps:

1. Derive current α. From the live pool’s dydx:

current_alpha = lp.math_pool.dydx(0, 1, use_fee=False)

This gives the marginal price of token0 in token1 units, fee-free, at the pool’s current state.

2. Compound the shock. new_alpha = current_alpha · (1 + price_change_pct).

3. Evaluate IL via the helper. Composed via StableswapImpLoss’s calc_iloss — same closed-form ε ↔ δ derivation that powers AnalyzeStableswapPosition and AssessDepegRisk, evaluated at the projected α.

Numeraire: peg (1:1 across tokens).

At-peg short-circuit. If current_alpha ≈ 1 (within tolerance), il_at_new_price returns the closed-form IL at 1 + price_change_pct directly without going through the fixed-point.

Unreachable handling. If new_alpha is unreachable for the pool’s A (the fixed-point has no |ε| < 1 solution), the primitive catches DepegUnreachableError and returns:

  • new_value = None
  • il_at_new_price = None
  • value_change_pct = None
  • token_names, A, new_price_ratio still populated

This lets callers tell the difference between “this depeg isn’t reachable for this A” and “this primitive failed.”

from defipy import SimulateStableswapPriceMove
from defipy.twin import MockProvider, StateTwinBuilder
provider = MockProvider()
builder = StateTwinBuilder()
lp_sts = builder.build(provider.snapshot("usdc_dai_stableswap_A10"))
# Stableswap is sensitive — model a 5% depeg.
result = SimulateStableswapPriceMove().apply(
lp_sts,
price_change_pct = -0.05,
lp_init_amt = 100.0,
)
print(f"token_names: {result.token_names}")
print(f"A: {result.A}")
print(f"new_price_ratio: {result.new_price_ratio:.6f}")
print(f"new_value: {result.new_value}")
print(f"il_at_new_price: {result.il_at_new_price}")
print(f"value_change_pct: {result.value_change_pct}")
token_names: ['USDC', 'DAI'] A: 10 new_price_ratio: 0.950000 new_value: 99.51570243887126 il_at_new_price: -0.004842975611287428 value_change_pct: -0.004842975611287414

A 5% depeg at A = 10 produces ~0.48% IL — measurably more than V2 would at the same shock (V2 at α = 0.95 gives ~0.033% IL). The amplification benefits stableswap near peg and hurts it off peg — which is exactly the design tradeoff AssessDepegRisk surfaces across multiple shocks.

  • Composes StableswapImpLoss evaluated at the projected α.
  • Independent of AssessDepegRisk — that primitive runs N depeg levels with V2 comparison; this one runs a single price shock with richer per-scenario fields.