100: Add Perps collateral, leverage, value, uPnL and gross-exposure totals in EMC
This commit is contained in:
+80
-8
@@ -1,6 +1,6 @@
|
||||
# Phoenix Perps read-only integration
|
||||
|
||||
`PhoenixPerpsService` and `PhoenixPerpsPosition` live in
|
||||
`PhoenixPerpsService`, `PhoenixPerpsAccount` and `PhoenixPerpsPosition` live in
|
||||
`com.r35157.libs.phoenix.perps`; the REST implementation is in `impl.ref`.
|
||||
They contain no Evelyn/UI types or signing/trading operations. NenjimHub creates
|
||||
one adapter without a portfolio cache as `phoenix.perps.default` and injects it into both Evelyn
|
||||
@@ -17,11 +17,13 @@ The implementation uses these official endpoints at `https://perp-api.phoenix.tr
|
||||
returns the wallet authority's version-1 snapshot and subaccounts. The
|
||||
[account model](https://docs.phoenix.trade/sdk/accounts) currently uses trader PDA
|
||||
index 0 for user accounts. Subaccount 0 is cross-margin; other returned subaccounts
|
||||
are isolated. Read every returned subaccount and include only nonzero
|
||||
`basePositionLots`. An omitted `positions` field is documented as optional and
|
||||
are isolated. `getAccounts()` returns actual balances separately from positions;
|
||||
`getOpenPositions()` remains a convenience flattening of those accounts. Keep funded
|
||||
empty Cross accounts in the overview and include only nonzero `basePositionLots`
|
||||
as position rows. An omitted `positions` field is documented as optional and
|
||||
means empty; null or a wrong type is rejected.
|
||||
2. [`GET /v1/view/exchange/markets`](https://docs.phoenix.trade/api/exchange/list-markets)
|
||||
supplies each symbol's `baseLotsDecimals`.
|
||||
supplies each symbol's `baseLotsDecimals`, `tickSize`, leverage tiers and taker fee.
|
||||
3. [`GET /v1/market/{symbol}/mark-price`](https://docs.phoenix.trade/api/exchange/get-mark-price)
|
||||
supplies `markPrice.price` in USD. The API permits an absent mark price; a held
|
||||
position without a valid positive price fails the lookup rather than appearing as zero.
|
||||
@@ -55,11 +57,14 @@ without a rounding context. Jackson parses JSON price numbers directly as decima
|
||||
without a `double` intermediate. For example, 2,144 SOL lots at `baseLotsDecimals=2`
|
||||
are 21.44 SOL; at a mark price of 104.45 USD, notional is 2,239.408 USD. Entry price,
|
||||
collateral, virtual quote lots and margin are not substitutes for current notional.
|
||||
Rounding to cents occurs only in the existing EMC display formatter.
|
||||
Rounding to cents occurs only in the EMC display formatter. Margin and estimated
|
||||
exit fees additionally follow the venue rounding described below.
|
||||
|
||||
Within each wallet's Phoenix venue, identity is `traderPdaIndex:subaccountIndex:symbol`.
|
||||
Sequence numbers do not affect identity. Rows sort by PDA index, subaccount index
|
||||
and symbol; labels use `SYMBOL-PERP`, with no extra subaccount tree level. Positive
|
||||
and symbol; labels use `SYMBOL-PERP`. Isolated rows remain directly beneath the venue.
|
||||
Cross positions belong beneath their explicit `Cross` account group, which supersedes
|
||||
the former no-subaccount-level restriction from issue #99. Positive
|
||||
quantity means Long and negative means Short. Thus two SOL subaccounts remain two
|
||||
rows even though their labels match.
|
||||
|
||||
@@ -74,12 +79,79 @@ Each Evelyn process performs an initial background refresh, then waits 60 second
|
||||
following each completed cycle. Jupiter and Phoenix are attempted independently for
|
||||
each configured wallet. Successful results replace only that wallet/venue; failures
|
||||
log wallet and venue and retain its last successful result. A valid empty result
|
||||
clears only that venue's positions, preserving its group. Snapshot reads never fetch
|
||||
replaces that venue's positions and account balances. EMC hides the venue heading
|
||||
when it has neither position rows nor Cross accounts; its read status remains in
|
||||
the wallet tooltip and the scope still contributes to snapshot totals.
|
||||
Funded Cross accounts remain visible when they no longer hold positions. Snapshot reads never fetch
|
||||
external data; EMC's three-second polling remains unchanged.
|
||||
|
||||
Positions and prices are separate REST reads and can belong to different slots.
|
||||
This is a periodic current-notional estimate, not an atomic exchange valuation.
|
||||
A cycle's duration includes all sequential reads and request pacing before the
|
||||
60-second pause (32 active markets need at least about 33 seconds for their reads). Failed
|
||||
reads can leave old data visible indefinitely; there is no stale/error indicator yet.
|
||||
reads can leave old data visible indefinitely. A visible venue shows `(retained)` after
|
||||
a failed refresh; cell tooltips include the last-success time. Childless venues are hidden,
|
||||
with failed or pending first-read status available in their wallet's tooltip. Such unknown
|
||||
scopes cannot contribute fabricated zero totals.
|
||||
Trader PDA indices other than 0, streaming and position management are out of scope.
|
||||
|
||||
|
||||
## Valuations, funding and margin
|
||||
|
||||
Monetary arithmetic uses exact `BigDecimal`/`BigInteger` values. Quote lots are
|
||||
1e-6 USD. The [official SDK computation](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/margin/compute.ts)
|
||||
and [PnL definition](https://docs.phoenix.trade/phoenix/margin-and-risk/entry-price-pnl)
|
||||
separate price PnL from funding and fees:
|
||||
|
||||
```text
|
||||
price uPnL = quantity × markPrice + virtualQuotePositionLots / 1e6
|
||||
funding = unsettledFundingQuoteLots / 1e6 (signed; positive adds equity)
|
||||
equity = collateral + sum(price uPnL + funding)
|
||||
exit fee = ceil_to_micro_USD(abs(quantity) × markPrice × takerFee × REST takerFeeOverrideMultiplier)
|
||||
Value = equity - sum(estimated exit fees)
|
||||
```
|
||||
|
||||
The REST override is already a decimal multiplier (observed 0.9), not the raw
|
||||
on-chain i8 representation. Missing override, fee, funding or virtual quote data
|
||||
makes affected valuations unavailable, rather than defaulting to zero. Settled
|
||||
trading fees and `accumulatedFundingQuoteLots` are not subtracted again; collateral
|
||||
already reflects settlement. Equity is undiscounted: no positive-uPnL risk discount
|
||||
or spot-collateral haircut is substituted for owned capital.
|
||||
|
||||
`collateral` is quote collateral in micro-USD. Each nonzero `spotCollaterals.balance`
|
||||
currently makes collateral/equity/Value unavailable: this version does not have a
|
||||
verified spot index-price valuation. It never quietly ignores SOL or another
|
||||
collateral asset. Zero spot balances do not change quote-only valuations. Missing
|
||||
collateral is also unavailable. Independent price PnL/exposure remain computable.
|
||||
|
||||
For a Cross position the Collateral column is its **initial margin requirement**,
|
||||
not a share of the account balance. The implementation follows the
|
||||
[SDK margin math](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/margin/math.ts)
|
||||
and [integer price conversion](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/units.ts):
|
||||
|
||||
```text
|
||||
markTicks = floor(markPrice × 10^(6-baseLotsDecimals) / tickSize)
|
||||
grossQuoteLots = abs(basePositionLots) × markTicks × tickSize
|
||||
L = first-tier leverage, or truncated linear interpolation of leverage between
|
||||
the surrounding maxSizeBaseLots bounds; beyond the final bound use 1
|
||||
initialMarginUsd = ceil(grossQuoteLots / L) / 1e6
|
||||
```
|
||||
|
||||
Interpolation truncates the complete positive leverage, including when leverage
|
||||
is decreasing. For 150 lots between (100 lots, 10×) and (200 lots, 5×), L is 7,
|
||||
not 7.5 or 8. At 100 USD and exponent 2, margin is 21.428572 USD. Exposure retains
|
||||
the exact original REST mark, preserving issue #99's current-notional meaning.
|
||||
|
||||
Cross account leverage is gross exposure / positive undiscounted equity before
|
||||
estimated exit fees. Cross position leverage and ROI are unavailable because the
|
||||
venue's displayed conventions have not been verified; the UI explains this and
|
||||
shows dollar PnL. Isolated leverage/ROI use actual collateral. Cross position
|
||||
Value is inapplicable; Cross account Value counts all shared capital once,
|
||||
including free collateral when its last position closes. Other aggregate ratios
|
||||
are blank. No account margin-utilization percentage is claimed; orders/risk
|
||||
adjustments are not included in such a metric.
|
||||
|
||||
Value is an estimate at separately read reference prices, not an executable quote.
|
||||
Slippage, price impact, swaps, network fees, account rent, future funding and
|
||||
unrelated wallet balances are excluded. No profit cap is applied to Phoenix.
|
||||
Unsupported/missing values and any higher totals requiring them show `—`.
|
||||
|
||||
Reference in New Issue
Block a user