Skip to content

Optimization

Optimization primitives answer “what’s the best version of this action?” — the optimal split for a single-sided deposit, the cost of a rebalance, the tradeoff across V3 tick ranges.

Three primitives:

  • OptimalDepositSplit — V2-only: optimal swap fraction for a single-sided zap-in
  • EvaluateRebalance — V2-only: cost of cycling a position (withdraw → swap → re-zap)
  • EvaluateTickRanges — V3-only: capital-efficiency vs IL-exposure vs fee-capture tradeoff across N candidate tick ranges

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 TickRangeCandidate
provider = MockProvider()
builder = StateTwinBuilder()
lp_v2 = builder.build(provider.snapshot("eth_dai_v2"))
lp_v3 = builder.build(provider.snapshot("eth_dai_v3"))
v2_tokens = lp_v2.factory.token_from_exchange[lp_v2.name]

Purpose. Compute the optimal swap fraction for a single-sided V2 deposit. Closed-form solution — no search, just the algebraic optimum.

Signature.

OptimalDepositSplit().apply(lp, token_in, amount_in) -> DepositSplitResult

Non-mutating projection (delegates to SwapDeposit._calc_univ2_deposit_portion). V3 raises ValueError. Slippage is reported in token-in units.

from defipy import OptimalDepositSplit
result = OptimalDepositSplit().apply(
lp_v2,
token_in = v2_tokens["ETH"],
amount_in = 100.0,
)
print(f"token_in: {result.token_in_name}")
print(f"amount_in: {result.amount_in}")
print(f"optimal_fraction: {result.optimal_fraction:.6f}")
print(f"swap_amount_in: {result.swap_amount_in:.4f}")
print(f"swap_amount_out: {result.swap_amount_out:.4f}")
print(f"deposit_amount_in: {result.deposit_amount_in:.4f}")
print(f"deposit_amount_out: {result.deposit_amount_out:.4f}")
print(f"expected_lp_tokens: {result.expected_lp_tokens:.4f}")
print(f"slippage_cost: {result.slippage_cost:.4f}")
print(f"slippage_pct: {result.slippage_pct:.6f}")
token_in: ETH amount_in: 100.0 optimal_fraction: 0.488822 swap_amount_in: 48.8822 swap_amount_out: 4647.0751 deposit_amount_in: 51.1178 deposit_amount_out: 4647.0751 expected_lp_tokens: 487.3553 slippage_cost: 241.1423 slippage_pct: 0.049331

Purpose. Report the all-in cost of cycling a V2 LP position — withdraw, swap to a single side, re-zap. Reports cost only; the rebalance verdict is left to the caller (or a downstream LLM).

Signature.

EvaluateRebalance().apply(lp, token_out, position_size_lp) -> RebalanceCostReport

V3 raises ValueError. Refuses to operate when the caller owns >99.9% of the pool (no counterparty for the swap leg).

from defipy import EvaluateRebalance
result = EvaluateRebalance().apply(
lp_v2,
token_out = v2_tokens["DAI"],
position_size_lp = 1000.0,
)
print(f"token_out: {result.token_out_name}")
print(f"current_value: {result.current_value:.4f}")
print(f"withdrawal_total_out: {result.withdrawal_total_out:.4f}")
print(f"withdrawal_slippage_pct: {result.withdrawal_slippage_pct:.6f}")
print(f"redeposit_slippage_pct: {result.redeposit_slippage_pct:.6f}")
print(f"total_slippage_cost: {result.total_slippage_cost:.4f}")
print(f"total_slippage_pct: {result.total_slippage_pct:.6f}")
print(f"expected_lp_tokens_after: {result.expected_lp_tokens_after:.4f}")
print(f"lp_delta: {result.lp_delta:.4f}")
token_out: DAI current_value: 20000.0000 withdrawal_total_out: 18975.6927 withdrawal_slippage_pct: 0.102431 redeposit_slippage_pct: 0.102431 total_slippage_cost: 2162.4265 total_slippage_pct: 0.108121 expected_lp_tokens_after: 997.0000 lp_delta: -3.0000

Purpose. Quantify the capital-efficiency vs IL-exposure vs fee-capture tradeoff across N V3 candidate tick ranges. Optionally compares one wide range against a split of N narrow ranges.

Signature.

EvaluateTickRanges(price_shock=0.10).apply(
lp, candidates, split_comparison=None,
) -> TickRangeEvaluation

candidates is a list of TickRangeCandidate(lwr_tick, upr_tick, name=None). optimal_range is the highest fee_capture_pct / il_exposure (with a 1e-9 floor to avoid divide-by-zero). All candidates must be in-range vs the current tick; out-of-range candidates raise ValueError.

from defipy import EvaluateTickRanges
# Current ETH/DAI tick ≈ 46054. Three candidate ranges around it:
candidates = [
TickRangeCandidate(lwr_tick=45000, upr_tick=47000, name="narrow"),
TickRangeCandidate(lwr_tick=44000, upr_tick=48000, name="medium"),
TickRangeCandidate(lwr_tick=-887220, upr_tick=887220, name="full_range"),
]
result = EvaluateTickRanges().apply(lp_v3, candidates)
print(f"price_shock used: {result.price_shock}")
print()
print(f"{'name':>12} {'cap_eff':>10} {'il_exposure':>12} {'fee_pct':>10} {'width_pct':>14}")
for r in result.ranges:
print(f"{r.name:>12} {r.capital_efficiency:>10.4f} {r.il_exposure:>12.6f} "
f"{r.fee_capture_pct:>10.6f} {r.range_width_pct:>14.4f}")
print()
print(f"optimal_range: {result.optimal_range.name}")
price_shock used: 0.1 name cap_eff il_exposure fee_pct width_pct narrow 10.5088 6.162096 0.001050 0.1992 medium 5.5169 0.174863 0.000551 0.4005 full_range 1.0000 0.001260 0.000100 3384921318552237922538062286262632448.0000 optimal_range: full_range
ProtocolSupportedNotes
Uniswap V2OptimalDepositSplit, EvaluateRebalance
Uniswap V3EvaluateTickRanges (V2/V3 zap variants pending UniV3Helper backlog for the others)
BalancerMulti-asset deposit semantics differ; not yet ported
StableswapSingle-sided deposits use a different objective; not yet ported

None of these are in the curated 10. Optimization primitives require multi-step reasoning — “is this rebalance worth it?”, “should I narrow my range?” — that’s better composed LLM-side over the leaf cost/scenario primitives. The category exists for when an agent needs the building blocks; the verdict is the LLM’s job.