100: Add Perps collateral, leverage, value, uPnL and gross-exposure totals in EMC
This commit is contained in:
+62
-28
@@ -435,38 +435,72 @@ Price Index beregnes og vises, mens de øvrige fem felter fortsat er nul. Legacy
|
||||
## 12. Perps-service og Mission Control
|
||||
|
||||
`EvelynPerpsService` udleverer et immutable `PerpsSnapshot` gennem det parameterløse
|
||||
`getPositionsSnapshot()`. Læsning foretager ingen integrationskald. Hub'en starter separate processer
|
||||
`evelyn.perps.prod` og `evelyn.perps.test`, injicerer de tilsvarende eksisterende `JupiterPerpsService`-views
|
||||
og en fælles `PhoenixPerpsService` uden positionscache registreret som `phoenix.perps.default`.
|
||||
`PerpsWalletConfiguration` indeholder en `SolanaWalletId`-adresse, der også bruges som række-ID og label.
|
||||
Hub'en ejer de hardcodede lister: seks PROD-adresser og én TEST-adresse fra issue #98.
|
||||
Begge integrationer læser de samme wallets i det pågældende miljø.
|
||||
`getPositionsSnapshot()`. Læsning foretager ingen integrationskald. Hub'en starter
|
||||
separate processer `evelyn.perps.prod` og `evelyn.perps.test`, hver med sin eksisterende
|
||||
wallet-liste: seks PROD-adresser og én TEST-adresse fra issue #98. Begge får en fælles
|
||||
read-only `JupiterPerpsOverview` og en fælles `PhoenixPerpsService`; sidstnævnte er
|
||||
registreret som `phoenix.perps.default`. De eksisterende Jupiter-services til handel
|
||||
og alarmer beholder deres afhængigheder og cache.
|
||||
|
||||
Servicen henter åbne Jupiter- og Phoenix-positioner asynkront ved start og venter 60 sekunder efter hvert
|
||||
fuldført gennemløb, så runder ikke overlapper. Den eksisterende Solana-cache med 60 sekunders TTL bevares
|
||||
for Jupiter. Phoenix læses gennem REST med 10 sekunders timeout pr. request, mindst ét sekund mellem
|
||||
request-start på tværs af miljøer og fælles cooldown ved HTTP 429. Data er periodiske snapshots.
|
||||
Servicen læser asynkront ved start og venter 60 sekunder efter hvert fuldført gennemløb.
|
||||
Runder overlapper ikke og afhænger ikke af EMC's synlighed. Jupiter-overblikket bruger
|
||||
ét REST-opslag pr. wallet. Phoenix bruger trader-state, metadata og én mark price pr.
|
||||
unikt åbent marked. Begge har 10 sekunders request-timeout; Phoenix bevarer fælles
|
||||
pacing og 429-cooldown. En fuld opdatering tager opslagstid plus pausen, ikke nødvendigvis
|
||||
ét minut. Implementeringens `close()` afbryder worker'en; consumer-API'et har kun læsning.
|
||||
|
||||
Jupiters positionskonto er stabilt ID; retning og `positionSize()` vises, kendte mints bliver
|
||||
SOL-PERP/BTC-PERP/ETH-PERP, og ukendte mints vises som adressen. Positioner sorteres efter konto.
|
||||
Phoenix bruger authority-wallet og trader PDA index 0 med alle returnerede cross/isolated-subaccounts.
|
||||
Kun nonzero-positioner medtages. ID og sortering bygger på PDA-index, subaccount-index og markedssymbol;
|
||||
sekvensnumre indgår ikke. Retningen kommer fra signed quantity, label er fx SOL-PERP, og USD-størrelsen er
|
||||
absolut base-mængde gange mark price, beregnet med decimalpræcision. Se [Phoenix-integrationen](PhoenixPerps.md)
|
||||
for dokumenterede enheder, endpoints og begrænsninger.
|
||||
Snapshot'et adskiller isolerede positioner fra Cross-konti med egne balancer. Pengebeløb
|
||||
er præcise USD-decimaler, og null betyder utilgængelig. Før første succes indeholder
|
||||
hver konfigureret wallet/venue en ukendt scope, ingen syntetiske positioner. Succes
|
||||
publiceres pr. wallet/venue; en fejl beholder den kombinations sidste observation og
|
||||
oprindelige succes-tidspunkt. Andre wallets/venues forsøges stadig. Et gyldigt tomt svar
|
||||
rydder den pågældende scope og kan totalisere nul; HTTP-/formatfejl kan ikke. En gyldig
|
||||
Cross-konto med kapital bevares, selv om dens sidste position lukkes.
|
||||
|
||||
Før første vellykkede opslag er der ingen positioner. Succes publiceres i et nyt snapshot pr. wallet/venue;
|
||||
fejl logges og bevarer netop den kombinations sidste data. De øvrige venues og wallets forsøges stadig.
|
||||
Et vellykket tomt svar fjerner kun den pågældende venues positioner og bevarer gruppen.
|
||||
Fejl i HTTP, authentication, svarformat eller konvertering er ikke tomme porteføljer.
|
||||
Der vises ingen indikator for forældede data. Implementationens `close()` afbryder worker'en;
|
||||
consumer-API'et indeholder kun læsning.
|
||||
EMC viser **Position | Direction | Margin | Collateral | Leverage | Exposure | Value | uPnL**
|
||||
under den synlige rod **Perpetual Contracts**. Isolerede positioner har wallet → venue →
|
||||
position; Phoenix Cross har wallet → venue → Cross → position. Positionens konto-ID
|
||||
bestemmer identitet og tilhørsforhold, aldrig instrumentteksten. Wallets følger den
|
||||
hidtidige konfiguration med Jupiter før Phoenix. En venue uden positioner eller Cross-konti
|
||||
skjules og vises igen, når der kommer indhold. Skjulte venues indgår fortsat i snapshot
|
||||
og totaler; deres læsestatus findes på wallet-rækkens tooltip. En Cross-konto med kapital
|
||||
holder derfor Phoenix synlig, selv uden åbne positioner. Eksisterende foldestatus, markering og
|
||||
rækkeobjekter bevares; uændrede snapshots opdaterer ikke træet. Vinduesstart er 1100 × 600,
|
||||
og smallere vinduer kan scrolle vandret, når kolonnernes minimumsbredder kræver det.
|
||||
|
||||
EMC spørger hver miljøservice straks ved åbning og hvert tredje sekund uden at blokere JavaFX-tråden.
|
||||
Skjul/luk stopper kun EMC-polling; services fortsætter. Træet er wallet → venue → position med Jupiter før
|
||||
Phoenix og uden et ekstra subaccount-niveau. Tre kolonner, farver, formatering, foldestatus og markering
|
||||
bevares for eksisterende rækker. Nye/lukkede positioner afstemmes efter ID.
|
||||
Der er ingen sample-data, Refresh-knap, handel, signering eller kontoaktivering i denne læsevej.
|
||||
Exposure er brutto: 100 USD Long + 100 USD Short giver 200 USD. Dollar-uPnL summeres
|
||||
med fortegn. Collateral/Value tæller hver isoleret position og hver delt Cross-konto én
|
||||
gang, inklusive ubrugt kapital på de inkluderede Cross-konti. Cross-positionernes
|
||||
marginkrav lægges ikke til faktisk kontokapital. Mangler en nødvendig del, er netop
|
||||
feltets total utilgængelig; en delsum præsenteres aldrig som total. Ingen summering eller
|
||||
gennemsnitsberegning af leverage eller PnL-procenter. Totalerne afhænger ikke af foldning.
|
||||
|
||||
Isoleret leverage er Exposure / positiv Collateral; uPnL-procent er 100 × pris-uPnL /
|
||||
positiv Collateral. På Cross-positioner er Collateral et initialt marginkrav i USD,
|
||||
ikke særskilt ejet kapital. Cross-gruppens leverage er bruttoeksponering / udiskonteret
|
||||
equity før estimerede exitgebyrer. Cross-positionernes leverage og ROI-procent er
|
||||
utilgængelige, fordi Phoenix-konventionerne ikke er verificeret; dollar-uPnL vises.
|
||||
Cross-positioners Value er ikke entydigt defineret og vises som —. Kontoudnyttelse er
|
||||
ikke implementeret.
|
||||
|
||||
Value er et estimat ved referencepriser, ikke et eksekverbart tilbud. Phoenix medregner
|
||||
collateral, pris-uPnL og signed uafregnet funding og fratrækker estimerede takergebyrer
|
||||
med kontoens fee override. Slippage, price impact, swaps, netværksgebyrer og kontorent
|
||||
indgår ikke. Ukendt spot-collateral gør de berørte værdiansættelser utilgængelige.
|
||||
**Jupiter Value er foreløbig utilgængelig**, fordi API'ets netværdi trækker åbningsgebyrer
|
||||
fra igen, og collateral-/profitloft-semantikken ikke er fuldt verificeret. Se
|
||||
[Jupiter-overblikket](JupiterPerpsOverview.md) og [Phoenix-integrationen](PhoenixPerps.md)
|
||||
for datakilder, enheder og beregninger.
|
||||
|
||||
Beløb har tusindtalsseparator og to decimaler, leverage × og pris-uPnL et synligt fortegn.
|
||||
Positiv uPnL er grøn, negativ rød, afrundet nul neutral uden negativt nul. De svage
|
||||
Long/Short-baggrunde bevares; grupper har tydeligere skrift og ingen retningsfarve.
|
||||
Tooltips forklarer definitioner, manglende værdier og sidste succesfulde læsetidspunkt.
|
||||
Efter fejl markeres en synlig venue som retained; læsestatus for skjulte venues findes
|
||||
på wallet-rækkens tooltip. Gamle tal beskrives ikke som nye.
|
||||
|
||||
EMC spørger hver miljøservice straks ved åbning og hvert tredje sekund uden at blokere
|
||||
JavaFX-tråden. Skjul/luk stopper kun EMC-polling. Der er ingen sample-data, Refresh-knap,
|
||||
handel, signering, kontoaktivering eller automatisk genstart af Hub'en i denne læsevej.
|
||||
Den kanoniske systembeskrivelse findes i
|
||||
[Evelyn: services og status](../../r35157_docs/projects/evelyn/README.md#evelynperpsservice).
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# Jupiter read-only Perps overview
|
||||
|
||||
`JupiterPerpsOverview` is an overview-only interface under
|
||||
`com.r35157.libs.jupiter.perps`; `impl.ref.RestJupiterPerpsOverview` reads
|
||||
`GET https://perps-api.jup.ag/v2/positions?walletAddress=<address>` once per wallet.
|
||||
The Hub injects this reader into both Evelyn Perps processes with their existing
|
||||
separate wallet lists. It has a 10-second request timeout, no immediate retries
|
||||
and no portfolio cache. Signing, transactions, alarms, their existing Solana cache
|
||||
and entry-price behavior remain in the existing `JupiterPerpsService`.
|
||||
|
||||
## Response contract and definitions
|
||||
|
||||
The official [CLI response type](https://github.com/jup-ag/cli/blob/main/src/clients/PerpsClient.ts)
|
||||
and [API reference](https://perps-api.jup.ag/v1/docs) describe the position list.
|
||||
The implementation requires an integer `count` matching the complete `dataList`,
|
||||
unique valid position accounts, valid asset mints, Long/Short side and positive
|
||||
size. HTTP/schema failures throw; only an explicit count-zero empty list clears
|
||||
the scope. Missing optional valuation fields become unavailable; malformed
|
||||
amounts fail the read.
|
||||
|
||||
| Display | Source / definition |
|
||||
| --- | --- |
|
||||
| Exposure | `sizeUsd / 10^6`; preserves the existing recorded USD-size basis |
|
||||
| Collateral | `collateralUsd / 10^6`, the venue's recorded post-fee USD collateral |
|
||||
| Leverage | Exposure / positive Collateral; not equity-based leverage |
|
||||
| uPnL | `pnlBeforeFeesUsd / 10^6`, price PnL before separately reported fees |
|
||||
| uPnL percent | 100 × uPnL / positive Collateral |
|
||||
| Value | Unavailable; API net-value semantics have not passed verification |
|
||||
|
||||
All amounts are parsed as decimal integers and converted exactly. The API's
|
||||
`pnlBeforeFeesPct` is already percentage points (95.48 means 95.48%), but the
|
||||
overview derives the requested percentage against precise collateral itself.
|
||||
The API's rounded `leverage` is likewise not used for the derived ratio.
|
||||
|
||||
## Why API Value is not displayed
|
||||
|
||||
On 24 October 2026 the funded configured wallet's six API sizes and collateral
|
||||
amounts matched read-only on-chain position data. For its SOL Short, the response
|
||||
contained these integer micro-USD values:
|
||||
|
||||
```text
|
||||
collateralUsd = 18559 pnlBeforeFeesUsd = 17721
|
||||
openFeesUsd = 361 closeFeesUsd = 361
|
||||
borrowFeesUsd = 7 valueUsd = 35551
|
||||
```
|
||||
|
||||
`35551 = 18559 + 17721 - 361 - 361 - 7`. The on-chain collateral was also
|
||||
18559; the official [position account documentation](https://developers.jup.ag/docs/perps/position-account)
|
||||
defines collateral as after fees. Blindly displaying this API Value would
|
||||
subtract the settled opening fee again. Adding it back alone does not establish
|
||||
the full contract: collateral-currency valuation and the `lockedAmount` profit
|
||||
cap must also be verified. The response does not expose that cap. The overview
|
||||
therefore shows an em dash with an explanation, and higher Value totals containing
|
||||
such a position also remain unavailable. It does not merge unmatched on-chain
|
||||
observations into the API snapshot or build unsigned closing transactions.
|
||||
|
||||
This is a known first-version limitation, not verified closing-value parity with
|
||||
Jupiter. Counts, amounts and prices above are observations, not fixed expectations.
|
||||
See the change's `verification.md` for observation times and current checks.
|
||||
+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