86 lines
5.3 KiB
Markdown
86 lines
5.3 KiB
Markdown
# 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.
|