Skip to content

fix(share-pnl): pin leverage to lease-open snapshot#165

Merged
metodi96 merged 4 commits into
mainfrom
fix/share-pnl-short-leverage
May 15, 2026
Merged

fix(share-pnl): pin leverage to lease-open snapshot#165
metodi96 merged 4 commits into
mainfrom
fix/share-pnl-short-leverage

Conversation

@metodi96
Copy link
Copy Markdown
Collaborator

Summary

Fix the share-pnl card's leverage calculation, which produced wildly inflated values for short positions (e.g. x12.3 displayed on a real 1.7x position) due to a dimensional mismatch between USD and LPN units. Pin the displayed value to the lease-open snapshot so it matches what the user actually opened at and doesn't drift with interest accrual or manual repayments.

Changes

Backend

  • backend/src/external/etl.rs — adds loan_amount_stable field on EtlLeaseInfo (renames from ETL's LS_loan_amnt_stable, the loan principal in stable USD at lease open).
  • backend/src/handlers/leases.rs — adds loan_amount_stable to LeaseEtlData and propagates it through the etl_info mapping.

Frontend

  • src/common/api/types/leases.ts — adds loan_amount_stable?: string to LeaseEtlData.
  • src/common/utils/LeaseCalculator.ts — adds leverageAtOpen: Dec | null to LeaseDisplayData; calculateDisplayData computes (downPayment + loanAtOpen) / downPayment from the frozen lease-open snapshot. Both fields are in stable USDC decimals (6), so the units line up across longs and shorts.
  • src/modules/leases/components/single-lease/SharePnLDialog.vueleverageMultiple prefers leaseDisplayData.leverageAtOpen. Falls back to a live computation using totalDebtUsd (instead of the previous totalDebt) when ETL hasn't exposed loan_amount_stable yet; this also fixes the dimensional bug in the fallback path for older leases.

Root cause

The previous leverageMultiple formula was (downPayment + totalDebt) / downPayment. downPayment came from parseEtlData and is denominated in USD (LS_cltr_amnt_stable: asset_micro_units × price, divided by collateral decimals). totalDebt came from calculateDebt and is denominated in LPN units (new Dec(lease.debt.principal, lpnDecimals)).

For longs the LPN is USDC ≈ $1, so the LPN-quantity ≈ USD value and the formula accidentally worked. For shorts the LPN is the volatile asset — on the OSMO short we verified, totalDebt ≈ 1072 OSMO was added to downPayment ≈ \$40.10 USD, producing a wildly inflated leverage.

Verification

  • cargo check and cargo clippy -- -D warnings on backend/ passed clean.
  • npx vue-tsc --noEmit and npx eslint on the changed files passed clean.
  • npx vitest run src/common/utils/LeaseCalculator.test.ts — all 73 existing tests still pass (no behavioral change in calculateDebt, parseEtlData, etc.; leverageAtOpen is a new additive field).
  • Verified the math against the live ETL data for nolus1y6j8c6u8z02f7jfe3wt3nyn7de8cjuk2ak79w5fxatzua8r62zwqufcg25 (the OSMO short used to track this down): LS_cltr_amnt_stable = $40.10, LS_loan_amnt_stable = $29.57, so leverageAtOpen = (40.10 + 29.57) / 40.10 = 1.74 — matches the protocol-true initial leverage and replaces the buggy x12.3 the card previously displayed.

Follow-ups

The dashboard / single-lease detail pages don't show leverage on the card so they're unaffected, but the new leverageAtOpen field is available on LeaseDisplayData if you ever want to surface a frozen-leverage badge elsewhere in the UI.

metodi96 added 4 commits May 15, 2026 13:30
The share-pnl card computed leverage as `(downPayment + totalDebt) / downPayment`
with downPayment in USD and totalDebt in LPN units — dimensionally correct only
for longs (LPN = USDC ≈ $1). For shorts the LPN is the volatile asset (OSMO,
WBTC, etc.) so totalDebt was a raw asset quantity, producing absurd leverages
like x12.3 on a real 1.7x position.

Plumb `LS_loan_amnt_stable` through from ETL (loan principal in stable USD at
open) and compute `leverageAtOpen = (downPayment + loanAtOpen) / downPayment`
once in calculateDisplayData. The share-pnl dialog prefers this frozen value
so the displayed leverage stays pinned to what the user actually opened at —
2.5x at the protocol max stays 2.5x for the life of the lease, regardless of
accruing interest or manual repayments. Falls back to a live computation
using totalDebtUsd (instead of totalDebt) when the lease pre-dates the new
field, fixing the dimensional bug even in the fallback path.
Mirrors the new field added to EtlLeaseInfo in the previous commit;
without it the `make_etl_with_collateral` helper fails to compile in
the test target.
Adds the new `LeaseEtlData.loan_amount_stable` field to the committed
OpenAPI spec snapshot. Generated via `UPDATE_OPENAPI_SNAPSHOT=1 cargo
test openapi`.
A real BTC short surfaced a denomination quirk: LS_loan_amnt_stable on
ETL /ls-opening is scaled by the LPN's native decimals (1e6 for OSMO,
1e8 for ALL_BTC, etc.), not by stable USDC decimals. The previous
`new Dec(loan_amount_stable, 6)` inflated the loan 100x on BTC shorts
and produced x76 leverage on a position actually opened at x1.75.

Pass `lpnDecimals` (already in scope from the surrounding
calculateDisplayData) to the Dec constructor so the scaling matches the
LPN's native decimals. OSMO continues to work because its LPN decimals
coincide with stable decimals.
@metodi96 metodi96 merged commit a0407f8 into main May 15, 2026
2 checks passed
@metodi96 metodi96 deleted the fix/share-pnl-short-leverage branch May 15, 2026 11:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant