Skip to content

Comparison

Comparison primitives answer “which is better for my position?” across choices that are otherwise apples-to-apples-ish.

Two primitives:

  • CompareFeeTiers — V3-only: compare candidate fee tiers for the same token pair
  • CompareProtocols — cross-protocol: IL and slippage for the same trade across two AMM types

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

from defipy.twin import MockProvider, StateTwinBuilder
from defipy.utils.data import FeeTierCandidate
provider = MockProvider()
builder = StateTwinBuilder()
lp_v2 = builder.build(provider.snapshot("eth_dai_v2"))
lp_v3 = builder.build(provider.snapshot("eth_dai_v3"))
lp_bal = builder.build(provider.snapshot("eth_dai_balancer_50_50"))
v2_tokens = lp_v2.factory.token_from_exchange[lp_v2.name]

Purpose. Compare V3 fee tiers for the same token pair. Mismatched token pairs raise ValueError; non-V3 pools raise ValueError.

Signature.

CompareFeeTiers().apply(candidates) -> FeeTierComparison

candidates is a list of FeeTierCandidate(lp, position_size_lp, lwr_tick, upr_tick, name=None). observed_fee_yield is None when the pool has no fees recorded / zero spot / zero TVL — None-yield candidates sort last in ranking_by_observed_fee_yield. position_size_lp is currently an echo (not used in v1 ranking).

Note: the canonical MockProvider exposes a single V3 ETH/DAI recipe (3000 bps fee), so the example below shows the API shape with one candidate. In practice you’d pass 2-3 candidates from different fee tiers (500 / 3000 / 10000) of the same pair to drive the ranking.

from defipy import CompareFeeTiers
candidates = [
FeeTierCandidate(
lp = lp_v3,
position_size_lp = 10000.0,
lwr_tick = 45000,
upr_tick = 47000,
name = "3000bps_narrow",
),
]
result = CompareFeeTiers().apply(candidates)
print(f"numeraire: {result.numeraire}")
print(f"pair: {result.pair}")
print(f"ranking_by_observed_fee_yield: {result.ranking_by_observed_fee_yield}")
print(f"ranking_by_tvl: {result.ranking_by_tvl}")
print()
for t in result.tiers:
print(f" {t.name}: fee_tier_bps={t.fee_tier_bps}, tvl={t.pool_tvl_in_token0:.4f}, "
f"yield={t.observed_fee_yield}, in_range={t.in_range}, width={t.range_width_pct:.4f}")
numeraire: ETH pair: ETH/DAI ranking_by_observed_fee_yield: ['3000bps_narrow'] ranking_by_tvl: ['3000bps_narrow'] 3000bps_narrow: fee_tier_bps=30, tvl=2000.0000, yield=None, in_range=True, width=0.1992

Purpose. Compare IL at a price shock + slippage at a trade size between two pools. Pools may be different protocols.

Signature.

CompareProtocols(price_shock=0.10, v3_range_pct=0.10).apply(
lp_a, lp_b, amount, token_in=None,
) -> ProtocolComparison

token_in defaults to lp_a’s token0 — both pools must contain it. V3 il_at_shock is None when price_shock > v3_range_pct (out-of-range regime). Stableswap il_at_shock is None on DepegUnreachableError. Balancer/Stableswap slippage_at_amount is None (CalculateSlippage is Uniswap-only). Advantage labels: "pool_a", "pool_b", "tied", or None (when either side is None).

from defipy import CompareProtocols
# Compare V2 against Balancer on the same ETH/DAI pair, 10 ETH trade.
result = CompareProtocols().apply(
lp_v2,
lp_bal,
amount = 10.0,
token_in = v2_tokens["ETH"],
)
print(f"price_shock used: {result.price_shock}")
print(f"amount / token_in: {result.amount} {result.token_in_name}")
print(f"il_advantage: {result.il_advantage}")
print(f"slippage_advantage: {result.slippage_advantage}")
print()
print(f" pool_a ({result.pool_a.protocol}): il={result.pool_a.il_at_shock}, "
f"slippage={result.pool_a.slippage_at_amount}, tvl={result.pool_a.tvl_in_token_in}")
print(f" pool_b ({result.pool_b.protocol}): il={result.pool_b.il_at_shock}, "
f"slippage={result.pool_b.slippage_at_amount}, tvl={result.pool_b.tvl_in_token_in}")
print()
print(f"notes: {result.notes}")
price_shock used: 0.1 amount / token_in: 10.0 ETH il_advantage: tied slippage_advantage: None pool_a (uniswap_v2): il=0.0012602161831160605, slippage=0.012841965602938644, tvl=2000.0 pool_b (balancer): il=0.0012602161831160605, slippage=None, tvl=2000.0 notes: ['pool_b: Balancer slippage not computed in v1 (CalculateSlippage is Uniswap-only)']
ProtocolSupportedNotes
Uniswap V2CompareProtocols (full)
Uniswap V3CompareFeeTiers (single-protocol); CompareProtocols (full, with v3_range_pct constraint)
Balancer⚠️CompareProtocols (IL only — slippage is None)
Stableswap⚠️CompareProtocols (IL only; None on unreachable depeg)

Neither primitive is in the curated 10. Comparison answers (“which fee tier?”, “which protocol?”) are second-order decisions an agent makes after a position-level analysis — they’re better composed LLM-side over the leaf primitives. The category exists for when an agent wants the structured comparison instead of building it from scratch.