Price Scenarios

Price Scenarios primitives project an LP position’s value at a hypothetical price change from the current pool state. Three primitives, one per protocol family: Uniswap V2/V3, Balancer (2-asset weighted), Stableswap (2-asset).

These are paper projections — no settlement-swap impact. Use them to answer “if ETH drops 30% from here, what’s my position worth?”

All primitives in the Agentic Primitives section follow the same contract: stateless construction, computation at .apply(), typed dataclass return.

Setup

[1]:
from defipy.twin import MockProvider, StateTwinBuilder

provider = MockProvider()
builder = StateTwinBuilder()

lp_v2 = builder.build(provider.snapshot("eth_dai_v2"))
lp_bal = builder.build(provider.snapshot("eth_dai_balancer_50_50"))
lp_sts = builder.build(provider.snapshot("usdc_dai_stableswap_A10"))

SimulatePriceMove

Purpose. Project a Uniswap V2/V3 LP position’s value at a hypothetical price change.

Signature.

SimulatePriceMove().apply(
    lp, price_change_pct, position_size_lp,
    lwr_tick=None, upr_tick=None,
) -> PriceMoveScenario

price_change_pct is a fraction (-0.30 = 30% drop), bounded by > -1.0. position_size_lp is in LP-token units; the result is scale-invariant in this size, so use any positive value. fee_projection is always None in v1 (no fee modeling). Numeraire is token0.

[2]:
from defipy import SimulatePriceMove

# 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,
)
[3]:
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

SimulateBalancerPriceMove

Purpose. Same projection adapted to a 2-asset Balancer weighted pool. The IL formula uses base/opp weights (not just the price ratio).

Signature.

SimulateBalancerPriceMove().apply(lp, price_change_pct, lp_init_amt) -> BalancerPriceMoveScenario

Numeraire is opp-token units. Raises ValueError if lp is not a BalancerExchange or pool is N>2 (propagated from BalancerImpLoss).

[4]:
from defipy import SimulateBalancerPriceMove

result = SimulateBalancerPriceMove().apply(
    lp_bal,
    price_change_pct = -0.30,
    lp_init_amt = 100.0,
)
[5]:
print(f"base / opp:         {result.base_tkn_name} / {result.opp_tkn_name}")
print(f"base_weight:        {result.base_weight}")
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}")
base / opp:         ETH / DAI
base_weight:        0.5
new_price_ratio:    0.700000
new_value:          167332.0053
il_at_new_price:    -0.015694
value_change_pct:   -0.163340

SimulateStableswapPriceMove

Purpose. Project a 2-asset stableswap LP position’s value at a hypothetical depeg. High A makes small price deviations costly.

Signature.

SimulateStableswapPriceMove().apply(lp, price_change_pct, lp_init_amt) -> StableswapPriceMoveScenario

Use small moves — stableswap is depeg-sensitive. Catches DepegUnreachableError: if the projected alpha is unreachable, new_value / il_at_new_price / value_change_pct come back as None (token_names, A, new_price_ratio still populated). At-peg short-circuit returns IL=0 exactly.

[6]:
from defipy import SimulateStableswapPriceMove

# Stableswap is sensitive — model a 5% depeg.
result = SimulateStableswapPriceMove().apply(
    lp_sts,
    price_change_pct = -0.05,
    lp_init_amt = 100.0,
)
[7]:
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

Protocol coverage

MCP tool exposure

All three primitives are surfaced as MCP tools in the curated set of 10:

  • SimulatePriceMove

  • SimulateBalancerPriceMove

  • SimulateStableswapPriceMove

Price-scenario questions (“what if ETH drops?”) are highest-traffic agent queries, so all three protocol variants are exposed directly rather than left to the LLM to compose.