99: Add read-only Phoenix Perps positions to EvelynPerpsService

This commit is contained in:
2026-10-23 12:11:22 +02:00
parent df5cc0eda7
commit 7e36b7ef95
17 changed files with 734 additions and 40 deletions
+25 -12
View File
@@ -436,24 +436,37 @@ Price Index beregnes og vises, mens de øvrige fem felter fortsat er nul. Legacy
`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` og injicerer de tilsvarende eksisterende `JupiterPerpsService`-views.
`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ø.
Servicen henter åbne Jupiter-positioner asynkront ved start og venter 60 sekunder efter hvert gennemløb,
så runder ikke overlapper. Den eksisterende Solana-cache med 60 sekunders TTL bevares. Data er derfor ikke
øjeblikkelige børsdata. Positionskontoen 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.
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.
Før første vellykkede opslag er der ingen positioner. Hver wallets succes publiceres i et nyt snapshot;
fejl logges og bevarer den wallets sidste data uden at afbryde de øvrige. Et vellykket tomt svar fjerner
positionerne, men bevarer wallet/Jupiter-gruppen. Der vises ingen indikator for forældede data.
Implementationens `close()` afbryder worker'en; consumer-API'et indeholder 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.
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 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æstruktur, tre kolonner, farver, formatering,
foldestatus og markering bevares for eksisterende rækker. Nye/lukkede positioner afstemmes efter ID.
Der er ingen sample-positioner, Phoenix-eksempler, sample-tekster, Refresh-knap eller handel.
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.
Den kanoniske systembeskrivelse findes i
[Evelyn: services og status](../../r35157_docs/projects/evelyn/README.md#evelynperpsservice).
+4 -1
View File
@@ -289,12 +289,15 @@ Mission Control kræver både `evelyn.perps.prod` og `evelyn.perps.test` før `e
i den private `conf/NenjimHub.conf`. Erstat den tidligere `evelyn.perps.default`-linje med de to nye ID'er;
det gamle ID accepteres ikke længere. Hub'en konstruerer, registrerer og starter to separate
`EvelynPerpsServiceImpl`-processer med separate hardcodede wallet-lister og tilhørende Jupiter-integrationer.
Begge får desuden den fælles `PhoenixPerpsService` uden positionscache, som Hub'en registrerer passivt som
`phoenix.perps.default`; den skal ikke tilføjes til `[AUTO_START]`.
Hub'en injicerer deres read-only
service-views til de tilsvarende miljøfaner i EMC. Process Manager viser begge ID'er efter vellykket startup.
Den lokale udviklingskonfiguration er opdateret; andre deployments med EMC skal migreres tilsvarende.
Udeladt eller forkert rækkefølge giver en tydelig konstruktionsfejl uden automatisk indsættelse.
Det minimale versionsstyrede bootstrap-eksempel er uændret. Hver service ejer sit eget snapshot og
opdaterer det fra Jupiter i baggrunden med 60 sekunders pause mellem gennemløb. EMC-læsninger bruger kun
opdaterer det fra Jupiter og Phoenix i baggrunden med 60 sekunders pause efter fuldførte gennemløb.
Sidste vellykkede data bevares separat pr. wallet og venue. EMC-læsninger bruger kun
det publicerede snapshot. Wallet-listerne fra issue #98 indeholder seks PROD-adresser og én TEST-adresse.
Hvis `evelyn.iou-burner.prod` vælges, skal både `assetaz.ticker.default` og
+85
View File
@@ -0,0 +1,85 @@
# Phoenix Perps read-only integration
`PhoenixPerpsService` 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
Perps processes. Each process owns its own wallet list, refresh worker and snapshot.
See [Evelyn](Evelyn.md#12-perps-service-og-mission-control) for consumer behavior and
[the canonical system overview](../../r35157_docs/projects/evelyn/README.md#evelynperpsservice)
for configuration and deployment status.
## REST reads and account model
The implementation uses these official endpoints at `https://perp-api.phoenix.trade`:
1. [`GET /v1/trader/state/{authority}?traderPdaIndex=0`](https://docs.phoenix.trade/api/trader/get-trader-state)
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
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`.
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.
An empty trader state needs only the first request. Otherwise metadata is fetched once
and each active symbol's price once per wallet lookup, including when that symbol is
held in several subaccounts. Request starts are spaced at least one second apart across both environments to avoid
bursts of per-market reads. Each HTTP request has a 10-second timeout and uses the
Hub's existing HTTP client (no redirects by default). On HTTP 429 the adapter respects
`Retry-After` (seconds or HTTP date; 60 seconds if missing/invalid). Further calls
fail fast during that shared cooldown, letting Jupiter and other wallet attempts
continue; the normal refresh cycle retries later. There are no automatic
retries, new API keys or authentication/signing flows. Public reads worked during
verification, despite generated API security annotations referring to bearer auth.
HTTP failures, including 401/403/404/429, are errors rather than empty portfolios.
The shared pacing follows Phoenix's [rate-limit guidance](https://docs.phoenix.trade/sdk/ws-api-best-practices);
it does not guarantee a fixed quota.
## Units, precision and identity
The official [SDK's `baseLotsToBaseUnits`](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/units.ts)
uses `lots / 10 ** baseLotsDecimals`; the exponent may be negative. The adapter uses:
```text
quantity = BigDecimal(basePositionLots).scaleByPowerOfTen(-baseLotsDecimals)
sizeUsd = abs(quantity) * markPrice.price
```
All lot counts use `BigInteger`; conversion and multiplication use `BigDecimal`
without a rounding context. Jackson parses JSON price numbers directly as decimal,
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.
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
quantity means Long and negative means Short. Thus two SOL subaccounts remain two
rows even though their labels match.
Authority/PDA identity, snapshot version, arrays, symbols, duplicate subaccounts or
positions, integer lots, metadata and prices are validated. A malformed lookup fails
as a whole; it never publishes a partially decoded portfolio. The shared Jackson
mapper is copied so strict parsing does not change other integrations.
## Refresh behavior and limitations
Each Evelyn process performs an initial background refresh, then waits 60 seconds
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
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.
Trader PDA indices other than 0, streaming and position management are out of scope.