AnalyzeStableswapPosition
AnalyzeStableswapPosition is the read-only, stateless primitive for 2-asset Stableswap pools. The Stableswap entry point with unreachable-alpha handling that propagates to portfolio totals — sibling to AnalyzePosition (V2/V3) and AnalyzeBalancerPosition.
The stableswap flat-curve regime around peg means small price deviations can produce surprisingly large IL at high A.
Signature at a glance
Section titled “Signature at a glance”| Protocol | Required call shape |
|---|---|
| Uniswap V2 | ❌ Use AnalyzePosition |
| Uniswap V3 | ❌ Use AnalyzePosition |
| Balancer | ❌ Use AnalyzeBalancerPosition |
| Stableswap | AnalyzeStableswapPosition().apply(lp, lp_init_amt, entry_amounts, holding_period_days=None) |
Common parameters
Section titled “Common parameters”| Parameter | Type | Description |
|---|---|---|
lp | StableswapExchange | 2-asset stableswap pool. N>2 raises ValueError (propagated from StableswapImpLoss). |
lp_init_amt | float | Pool shares held by the position. |
entry_amounts | list[float] | Per-token entry amounts in pool insertion order, e.g. [x0, x1]. Different shape from V2/V3/Balancer’s paired entry_x/y — covers single-sided deposits, not just balanced ones. |
holding_period_days | float (optional) | Holding period in days; enables real_apr annualization. |
Mathematical contract
Section titled “Mathematical contract”Composes StableswapImpLoss for the closed-form IL math via the ε ↔ δ derivation over the stableswap invariant. Same primitive composition pattern as AssessDepegRisk — see that page for the full derivation walkthrough.
Numeraire: peg (1:1 across tokens). All values are denominated at peg — stableswap’s natural numeraire (no divergence ⇒ tokens equivalent in value), matching StableswapImpLoss.hold_value(). Callers wanting a non-peg numeraire rebase manually.
At-peg short-circuit. When the pool’s implied |1 − dydx| < 1e-12, the primitive treats it as perfectly balanced and skips the fixed-point IL computation:
il_percentage = 0.0diagnosis = "at_peg"
Both for performance and to avoid the ε = 0 degenerate case bouncing through the solver.
Unreachable-alpha handling. At high A, small |1 − α| shocks may be unreachable (|ε| ≥ 0.95 or wherever the solver bounds it). When the current pool state implies such an alpha, the primitive catches DepegUnreachableError and returns:
il_percentage = Nonenet_pnl = Nonediagnosis = "unreachable_alpha"current_value = hold_value(conservatively — without IL we can’t back out a per-token composition)
This matches the None-sentinel convention used by AssessDepegRisk and CompareProtocols. In practice it’s rare for a pool’s own state to drift past reachability (self-consistency of the invariant keeps it inside), but honest handling avoids silent surprises.
entry_amounts rationale. Takes a list [x0, x1] instead of paired entry_x_amt/entry_y_amt because stableswap commonly has single-sided deposits — [x, 0] or [0, y] — that aren’t naturally paired. Setting one entry to zero captures a single-sided deposit cleanly.
No fee attribution in v1. Stableswap’s self.tkn_fees is pool-global with no per-LP attribution; same scope stance as AnalyzeBalancerPosition. fee_income = 0.0 always.
Example
Section titled “Example”from defipy import AnalyzeStableswapPositionfrom defipy.twin import MockProvider, StateTwinBuilderfrom stableswappy.process.swap import Swap as StableswapSwap
provider = MockProvider()builder = StateTwinBuilder()lp_sts = builder.build(provider.snapshot("usdc_dai_stableswap_A10"))sts_tokens = lp_sts.factory.token_from_exchange[lp_sts.name]
# Push slightly off peg so the analysis isn't trivially zero.StableswapSwap().apply(lp_sts, sts_tokens["USDC"], sts_tokens["DAI"], "agent", 5000.0)
result = AnalyzeStableswapPosition().apply( lp_sts, lp_init_amt = 100.0, entry_amounts = [100000.0, 100000.0], holding_period_days = 30.0,)
print(f"token_names: {result.token_names}")print(f"A: {result.A}")print(f"alpha: {result.alpha:.6f}")print(f"current_value: {result.current_value:.6f}")print(f"hold_value: {result.hold_value:.4f}")print(f"il_percentage: {result.il_percentage:.8f}")print(f"net_pnl: {result.net_pnl:.6f}")print(f"diagnosis: {result.diagnosis}")A 1% off-peg drift at A = 10 produces ~0.017% IL on a $200k position — small in absolute terms, but flagged as il_dominant because there are no fees in this scenario to compensate.
How this composes
Section titled “How this composes”- Composes
StableswapImpLossfor the closed-form IL via the ε ↔ δ derivation. Same composition pattern asAssessDepegRisk. - Composed into by
AggregatePortfolio— unreachable-alpha positions contribute0.0to portfolio totals and append a note toshared_exposure_warnings.
See also
Section titled “See also”AnalyzePosition— V2/V3 siblingAnalyzeBalancerPosition— Balancer siblingSimulateStableswapPriceMove— projection counterpartAssessDepegRisk— multi-level depeg-risk surfaceAggregatePortfolio— multi-position aggregation with unreachable-alpha handling- Stableswap math — invariant + ε ↔ δ relation
- The Primitive Contract — cross-cutting invariants
- MCP tool exposure: Curated v2.0 toolset — surfaced as the Stableswap entry point for “how is my position doing?”.