104: EMC Perps: show estimated Jupiter Value and mark affected totals with ≈
This commit is contained in:
+15
-2
@@ -505,8 +505,21 @@ Value er et estimat ved referencepriser, ikke et eksekverbart tilbud. Phoenix me
|
||||
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
|
||||
Jupiters rapporterede `valueUsd` vises uændret som et estimat, fx `≈$123.23`, efter
|
||||
præcis konvertering fra heltals-mikro-USD. Tooltip forklarer, at det faktiske provenu
|
||||
ved lukning kan afvige. En tidligere observation trak åbningsgebyrer fra, selvom
|
||||
collateral allerede var registreret efter gebyrer; gebyrbehandlingen er derfor
|
||||
usikker, og collateral-/profitloft-semantikken er fortsat ikke verificeret. Det
|
||||
beviser ikke, at alle API-estimater er forkerte, eller at forskellen kun er små gebyrer.
|
||||
Der tilføjes ingen gebyrer tilbage og foretages ingen korrigeret netberegning.
|
||||
|
||||
Tilgængelige Value-estimater fra både Jupiter og Phoenix samt deres venue-, wallet-
|
||||
og **Perpetual Contracts**-totaler har ét `≈` foran beløbet. Totalens tooltip forklarer
|
||||
de bidragende estimatkilder, også ved blandede venues og sammenfoldede grupper.
|
||||
Nul og negative beløb bevares. Manglende/null Value gør fortsat den berørte total `—`
|
||||
uden numerisk estimatmarkering; en succesfuldt læst tom gruppe kan give nul. Ugyldige
|
||||
beløb fejler hele observationen, så tidligere data og oprindelig læsetid bevares med
|
||||
fejlstatus. En ny succes erstatter den tidligere værdi, også når den nye Value mangler. Se
|
||||
[Jupiter-overblikket](JupiterPerpsOverview.md) og [Phoenix-integrationen](PhoenixPerps.md)
|
||||
for datakilder, enheder og beregninger.
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ amounts fail the read.
|
||||
| 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 |
|
||||
| Value | `valueUsd / 10^6`, Jupiter's reported estimate, unchanged and displayed with `≈` |
|
||||
| Risk reference price | Positive `markPriceUsd / 10^6` from the same response |
|
||||
| Reported liquidation price | Positive `liquidationPriceUsd / 10^6`; missing/nonpositive values are unavailable |
|
||||
|
||||
@@ -42,7 +42,26 @@ metrics. Present malformed amounts still fail the read. Isolated signed distance
|
||||
the owner's neutral-distance policy and expiration are described in
|
||||
[Perps risk presentation](Evelyn.md#risiko-i-perps).
|
||||
|
||||
## Why API Value is not displayed
|
||||
## Reported Value and its uncertainty
|
||||
|
||||
The overview preserves optional `valueUsd` exactly as supplied, including zero
|
||||
and signed values. EMC displays it with `≈` and two USD decimals (for example,
|
||||
`≈$123.23`). Its tooltip explains: “Estimated position value reported by Jupiter.
|
||||
Actual proceeds on closing may differ.” Missing/null values remain unavailable;
|
||||
present malformed amounts fail the complete observation. Successful observations
|
||||
replace earlier values, including replacing an estimate with unavailable Value.
|
||||
Failed reads retain the original observation, success time and failure indication.
|
||||
|
||||
Available Jupiter, wallet and Perpetual Contracts Value totals inherit the estimate
|
||||
qualification independently of expansion state. Mixed Phoenix/Jupiter totals
|
||||
identify both estimate sources; Phoenix's existing account-aware closing estimates
|
||||
and single counting of shared Cross capital are preserved. A missing required Value
|
||||
still makes the affected total `—`, without a numeric approximation marker. A
|
||||
successfully read empty scope can total zero.
|
||||
|
||||
This reports a venue estimate, not verified closing proceeds. It does not add
|
||||
opening fees back, correct the API number, introduce closing quotes or combine
|
||||
additional on-chain observations with the response.
|
||||
|
||||
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
|
||||
@@ -56,14 +75,15 @@ 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
|
||||
defines collateral as after fees. This observation raises uncertainty about
|
||||
subtracting a settled opening fee again; it does not prove that every API estimate
|
||||
is wrong. Adding that fee back alone would 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.
|
||||
cap remain unverified. The response does not expose that cap. The possible difference
|
||||
from actual closing proceeds is not known to be limited to small fees. Issue #100
|
||||
therefore withheld Value; [issue #104](https://git.r35157.com/r35157/com_r35157_nenjim-hubd-impl_ref/issues/104)
|
||||
replaces that presentation with the explicitly qualified venue estimate.
|
||||
|
||||
This is a known first-version limitation, not verified closing-value parity with
|
||||
Jupiter. Counts, amounts and prices above are observations, not fixed expectations.
|
||||
There is no verified closing-value parity with Jupiter. Counts, amounts and prices
|
||||
above are historical observations, not fixed expectations.
|
||||
See the change's `verification.md` for observation times and current checks.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-10-26
|
||||
@@ -0,0 +1,29 @@
|
||||
# Design
|
||||
|
||||
## Context
|
||||
|
||||
See proposal.md for motivation. Jupiter already parses optional micro-USD fields through strict decimal-integer validation. Immutable snapshot Metrics own completeness-aware totals; venue totals add isolated positions and shared Cross accounts once. EMC formats those totals independently of visible descendants.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:** Carry numeric Value and its estimate provenance together through the existing pipeline, preserving original observation/failure semantics and source compatibility for existing constructors.
|
||||
|
||||
**Non-Goals:** Correcting Jupiter fees, verifying closing proceeds, adding network requests, changing Phoenix valuation formulas, or modifying refresh/configuration.
|
||||
|
||||
## Decisions
|
||||
|
||||
- Extend the overview position with nullable USD Value, using the existing exact micro-USD parser. Retain earlier constructors with unavailable Value defaults. An independent decimal parser would risk a different validation contract.
|
||||
- Add an immutable set of Value estimate sources to Metrics (Jupiter/Phoenix). Mark each integration's available closing Values and union sources in totals. A single boolean would lose the source-specific explanation at mixed wallet/root levels; deriving qualification from rendered descendants would fail when groups are hidden/collapsed.
|
||||
- Estimate sources survive incomplete totals so their tooltips can still explain the contributions. Numeric availability controls whether a marker is shown. Totals still apply the existing all-components-required rule. Empty sums remain zero. Source sets do not affect other metrics or Cross account ownership.
|
||||
- Format Value with a single prefix only for available qualified amounts. Add source explanations to the Value cell tooltip from its Metrics, so ancestors do not depend on descendant notes. Preserve the existing position notes, read status and USD rounding.
|
||||
- Verify with temporary deterministic HTTP/service/JavaFX harnesses outside the repository, following the project's separate test-artifact policy. Record checks in verification.md.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- Reported Jupiter estimates may differ materially from closing proceeds → use the number as supplied and describe observed fee uncertainty plus unverified collateral/profit-cap semantics at contributing scopes.
|
||||
- Losing qualification during aggregation → keep provenance in immutable Metrics and verify mixed, missing, zero and collapsed totals.
|
||||
- Mistaking retained values for new observations → keep the existing whole-venue retention path and verify original timestamps through failure/recovery.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
No stored schema or configuration migration. Rebuild the existing source; retained constructors support current callers. Deploy/restart remains outside this implementation task. Reverting the source restores the previous presentation.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Proposal
|
||||
|
||||
## Why
|
||||
|
||||
Evelyn's capital overview currently hides Jupiter's reported position Value and every dependent total. Issue #104 requests the venue estimate with an explicit approximation qualification so the owner can inspect capital without mistaking it for guaranteed closing proceeds.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Preserve nullable Jupiter `valueUsd`, converting integer micro-USD exactly without fee corrections.
|
||||
- Carry immutable Value estimate sources through complete, account-aware totals, including Phoenix's existing closing estimates.
|
||||
- Display available estimates and affected totals with one `≈`, two USD decimals and source-specific explanations; incomplete totals remain `—`.
|
||||
- Preserve polling, failure retention, observation times, Cross capital accounting and independent position metrics.
|
||||
- Update current project and shared Evelyn documentation, preserving the valuation uncertainties.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
None.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `jupiter-perps-overview`: Preserve the reported estimate and validate its optional integer amount.
|
||||
- `evelyn-perps-service`: Publish estimate sources and propagate them through complete totals and retained observations.
|
||||
- `evelyn-perps-overview`: Format approximate Value and explain Jupiter/Phoenix contributions at every level.
|
||||
|
||||
## Impact
|
||||
|
||||
Changes affect the Jupiter overview position record/reader, Perps snapshot/service, EMC Value presentation, `docs/JupiterPerpsOverview.md`, `docs/Evelyn.md` and the canonical shared Evelyn page in `r35157_docs`. Existing constructors remain available. No new endpoint, price source, dependency, signing or trading operation is introduced. Implementation remains uncommitted and unpushed.
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Estimated Value is visibly qualified at every level
|
||||
Available estimated Value SHALL show one `≈` before USD formatting with two decimals, including signed/zero values and every affected venue, wallet and root total. Unavailable Value SHALL show `—` without a numeric approximation marker. Tooltips SHALL identify contributing estimate sources and distinguish estimates from actual closing proceeds. Existing Phoenix account calculations and all independent columns SHALL remain intact.
|
||||
|
||||
#### Scenario: Jupiter position tooltip
|
||||
- **WHEN** the owner inspects Jupiter position Value
|
||||
- **THEN** its tooltip says "Estimated position value reported by Jupiter. Actual proceeds on closing may differ."
|
||||
- **AND** it explains the observed opening-fee treatment uncertainty and unverified collateral/profit-cap semantics without claiming differences are always small or every response is wrong
|
||||
|
||||
#### Scenario: Rounding and affected ancestors
|
||||
- **WHEN** a Jupiter position Value is 123.234567 USD
|
||||
- **THEN** its Value displays `≈$123.23`
|
||||
- **AND** Jupiter, wallet and root totals each display one `≈` and explain the Jupiter contribution, even when collapsed
|
||||
|
||||
#### Scenario: Phoenix and incomplete totals
|
||||
- **WHEN** Phoenix estimates contribute to a mixed portfolio
|
||||
- **THEN** available affected totals also explain Phoenix's estimated closing Value
|
||||
- **AND** a missing required contribution leaves the affected total `—` without `≈`
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Value estimates preserve their sources through totals
|
||||
Snapshots SHALL preserve Jupiter's reported Value and identify available Jupiter and Phoenix Values as estimates. Every complete Value total SHALL inherit its contributing estimate sources independently of UI expansion. Missing required values SHALL keep totals unavailable. Cross capital SHALL count once. Failed reads SHALL retain the prior values, qualifications and original times with failure status; success SHALL replace them.
|
||||
|
||||
#### Scenario: Mixed complete totals
|
||||
- **WHEN** a wallet has a Jupiter estimate of 12 USD and a Phoenix Cross account estimate of 84 USD with multiple children
|
||||
- **THEN** wallet and root Value are 96 USD and identify both estimate sources
|
||||
- **AND** collapsing any group changes neither total nor qualification
|
||||
|
||||
#### Scenario: Missing contribution and successful empty scope
|
||||
- **WHEN** a required Jupiter Value is missing
|
||||
- **THEN** the dependent Value totals remain unavailable without losing other metrics
|
||||
- **AND** a successfully read empty scope can still contribute zero
|
||||
|
||||
#### Scenario: Failed estimate and recovery
|
||||
- **WHEN** a valid Jupiter estimate is followed by a malformed observation
|
||||
- **THEN** the original estimate, its qualification and success/observation times remain with failure status
|
||||
- **AND** successful subsequent observations replace the estimate, including replacing it with missing Value or clearing an empty scope
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Verified metrics and unavailable valuations
|
||||
The overview SHALL preserve size basis, post-fee USD collateral and price PnL before separate fees, with leverage/ROI against positive collateral. It SHALL preserve optional reported `valueUsd` as an unverified estimate, converting integer micro-USD exactly without fee corrections. Missing/null Value SHALL remain unavailable; present malformed amounts SHALL fail the observation. Other valid metrics SHALL survive missing Value.
|
||||
|
||||
#### Scenario: Unverified API closing value
|
||||
- **WHEN** API Value subtracts opening fees already settled in collateral or does not establish profit-cap valuation
|
||||
- **THEN** the reported Value is preserved unchanged as an unverified estimate while independent metrics remain available
|
||||
- **AND** it is not represented as guaranteed closing proceeds or corrected by adding opening fees back
|
||||
|
||||
#### Scenario: Reported API closing estimate
|
||||
- **WHEN** Jupiter reports `valueUsd` as `"123234567"`, `"0"` or `"-1234567"`
|
||||
- **THEN** the overview preserves exactly 123.234567, 0 or -1.234567 USD respectively
|
||||
- **AND** it does not add opening fees back or impose a profit-cap correction
|
||||
|
||||
#### Scenario: Missing or malformed Value
|
||||
- **WHEN** `valueUsd` is missing or null
|
||||
- **THEN** Value is unavailable while independent metrics remain available
|
||||
- **AND** a present malformed amount instead fails the complete observation
|
||||
|
||||
#### Scenario: Nonpositive collateral
|
||||
- **WHEN** a position's collateral is zero or negative
|
||||
- **THEN** its leverage and PnL percentage are unavailable
|
||||
@@ -0,0 +1,20 @@
|
||||
# Tasks
|
||||
|
||||
## 1. Exact Jupiter estimate observation
|
||||
|
||||
- [x] 1.1 Preserve nullable reported Value through the overview record/reader; verify exact micro-USD conversion, large/signed/zero/missing/null amounts and malformed observation rejection with temporary fixtures.
|
||||
- [x] 1.2 Update JupiterPerpsOverview.md with the reported-estimate contract and historical fee uncertainty; verify obsolete unavailable-only claims are removed.
|
||||
|
||||
## 2. Immutable estimate provenance and complete totals
|
||||
|
||||
- [x] 2.1 Map Jupiter and Phoenix estimate sources into immutable Metrics and propagate complete totals; verify Jupiter/wallet/root and mixed Cross totals, missing components, empty scopes and source immutability.
|
||||
- [x] 2.2 Verify real service failure/retention/recovery using deterministic fixtures, including original timestamps, replacement by missing Value and empty scope clearing; record results in verification.md.
|
||||
|
||||
## 3. EMC Value presentation and current documentation
|
||||
|
||||
- [x] 3.1 Format available estimated Value with one approximation prefix and source-specific tooltips; verify USD rounding, signed/zero/missing values, ancestor tooltips, collapsed groups and retained/recovered rows in a temporary JavaFX harness.
|
||||
- [x] 3.2 Update docs/Evelyn.md and r35157_docs/projects/evelyn/README.md; verify estimate limitations, source links and consistency with the implementation.
|
||||
|
||||
## 4. Integration validation
|
||||
|
||||
- [x] 4.1 Run Gradle build and strict OpenSpec validation, inspect the final diff and record verification evidence; confirm no implementation commit/push and preserve pre-existing changes.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Verification — issue #104
|
||||
|
||||
Verified locally on 2026-10-26. Implementation remains uncommitted and unpushed.
|
||||
No deployment, Hub restart, signing, trading, new venue request or price source.
|
||||
|
||||
## Delivered behavior
|
||||
|
||||
- The existing Jupiter overview GET preserves optional `valueUsd` as an exact decimal conversion from integer micro-USD. Zero, signed and large values survive; missing/null remains unavailable and malformed fields fail the complete observation. The reported number is used unchanged.
|
||||
- Immutable Metrics carry Jupiter/Phoenix estimate sources through account-aware venue, wallet and root totals. Sources are defensively copied. Shared Cross balances are counted once and position-only Cross Value remains inapplicable. Incomplete totals remain unavailable; their tooltips still explain the contributing estimate sources.
|
||||
- Available estimated Value displays one `≈` before the existing USD formatting and two-decimal rounding. Missing values show `—` without a numeric marker. Source-specific Value tooltips explain Jupiter's reported estimate and valuation uncertainty, Phoenix's estimated closing Value, and the difference from actual closing proceeds.
|
||||
- Whole-venue failure retention keeps original positions, source qualifications, observation time and last-success time. Successful recovery replaces observations, including replacing old Value with missing Value or clearing a successful empty scope. No polling/request/configuration behavior changed.
|
||||
- Current repository documentation and `r35157_docs/projects/evelyn/README.md` describe the new behavior and retain the real valuation limitations. The three affected main OpenSpec specifications are synced. Historical archived #100 documentation remains historical.
|
||||
|
||||
## Checks
|
||||
|
||||
- `JAVA_HOME=/usr/local/software/java/jfx-25 ./gradlew build`: successful; 42 existing tests, zero failures/errors/skips.
|
||||
- `/tmp/Issue104Check.java`: 63 focused assertions passed against the actual Jupiter HTTP reader, immutable model and background service using local HTTP fixtures. Covers exact `123234567 → 123.234567`, zero/negative/above-binary-integer-precision amounts, missing/null, eleven malformed representations, unchanged historical fee example, stable independent metrics, Jupiter and mixed totals, Cross and isolated Phoenix, defensive source copies, failure retention and replacement/empty recovery.
|
||||
- `/tmp/Issue104Preview.java`: 60 JavaFX assertions passed against actual table cells and tooltips. This is an assertion count, not 60 distinct user scenarios. Covers `≈$123.23`, `≈$0.00`, `≈-$1.23`, half-up rounding and tiny negative rounding to unsigned zero; complete and unavailable ancestor totals; Jupiter/Phoenix source explanations; Cross accounting; collapsed venue/Cross/wallet/root groups; unchanged rows/selection; retained/read-failed/recovered observations; empty and unknown hidden venues; independent Exposure/Collateral/Leverage/uPnL/Risk/TWAP and direction styles; horizontal scrolling.
|
||||
- Visually inspected `/tmp/issue104-check/value-expanded.png`, `value-collapsed.png`, `value-incomplete.png`, `value-total-tooltip.png` and `value-small-window.png`. Value was horizontally scrolled into the viewport before the final captures; the mixed tooltip is readable and bounded.
|
||||
- Existing temporary `/tmp/Issue102Check.java` and `/tmp/Issue103Check.java` passed their 68 risk and 32 TWAP assertions respectively. No permanent test sources or dependencies were added.
|
||||
- Strict validation passes for `104-jupiter-estimated-value` and main specifications `jupiter-perps-overview`, `evelyn-perps-service` and `evelyn-perps-overview`. Existing long paragraphs in the overview specification emit informational length hints only.
|
||||
- Working-tree and change-artifact whitespace checks pass, and all 22 local documentation links resolve, including the new Jupiter section anchor. The 22 pre-existing staged/deleted OpenSpec artifacts for #97, #99 and #103 remain untouched; no issue-104 file was staged.
|
||||
|
||||
## Limits
|
||||
|
||||
Verification uses deterministic local fixtures, not live closing quotes or deployment to the running Hub. It establishes the reported-value pipeline, qualification and retention behavior, not correctness of Jupiter's valuation or equality with actual closing proceeds. The 24 October fee example in the Jupiter documentation is a preserved historical observation.
|
||||
@@ -147,3 +147,21 @@ Warning tooltips SHALL identify wallet/account/position, reason, TWAP address, a
|
||||
- **WHEN** a warned venue or consumer read fails, or its original observation becomes five minutes old
|
||||
- **THEN** source and inherited warnings remain visible with retained/stale qualification and original times
|
||||
- **AND** existing UI polling updates age without additional venue reads or resetting selection and expansion
|
||||
|
||||
### Requirement: Estimated Value is visibly qualified at every level
|
||||
Available estimated Value SHALL show one `≈` before USD formatting with two decimals, including signed/zero values and every affected venue, wallet and root total. Unavailable Value SHALL show `—` without a numeric approximation marker. Tooltips SHALL identify contributing estimate sources and distinguish estimates from actual closing proceeds. Existing Phoenix account calculations and all independent columns SHALL remain intact.
|
||||
|
||||
#### Scenario: Jupiter position tooltip
|
||||
- **WHEN** the owner inspects Jupiter position Value
|
||||
- **THEN** its tooltip says "Estimated position value reported by Jupiter. Actual proceeds on closing may differ."
|
||||
- **AND** it explains the observed opening-fee treatment uncertainty and unverified collateral/profit-cap semantics without claiming differences are always small or every response is wrong
|
||||
|
||||
#### Scenario: Rounding and affected ancestors
|
||||
- **WHEN** a Jupiter position Value is 123.234567 USD
|
||||
- **THEN** its Value displays `≈$123.23`
|
||||
- **AND** Jupiter, wallet and root totals each display one `≈` and explain the Jupiter contribution, even when collapsed
|
||||
|
||||
#### Scenario: Phoenix and incomplete totals
|
||||
- **WHEN** Phoenix estimates contribute to a mixed portfolio
|
||||
- **THEN** available affected totals also explain Phoenix's estimated closing Value
|
||||
- **AND** a missing required contribution leaves the affected total `—` without `≈`
|
||||
|
||||
@@ -119,3 +119,21 @@ Snapshots SHALL preserve isolated risk prices and Cross account tiers/thresholds
|
||||
- **WHEN** isolated distance reaches/crosses zero or effective collateral is nonpositive
|
||||
- **THEN** the signed boundary is preserved or MM percentage is unavailable respectively
|
||||
- **AND** display rounding does not determine risk classification
|
||||
|
||||
### Requirement: Value estimates preserve their sources through totals
|
||||
Snapshots SHALL preserve Jupiter's reported Value and identify available Jupiter and Phoenix Values as estimates. Every complete Value total SHALL inherit its contributing estimate sources independently of UI expansion. Missing required values SHALL keep totals unavailable. Cross capital SHALL count once. Failed reads SHALL retain the prior values, qualifications and original times with failure status; success SHALL replace them.
|
||||
|
||||
#### Scenario: Mixed complete totals
|
||||
- **WHEN** a wallet has a Jupiter estimate of 12 USD and a Phoenix Cross account estimate of 84 USD with multiple children
|
||||
- **THEN** wallet and root Value are 96 USD and identify both estimate sources
|
||||
- **AND** collapsing any group changes neither total nor qualification
|
||||
|
||||
#### Scenario: Missing contribution and successful empty scope
|
||||
- **WHEN** a required Jupiter Value is missing
|
||||
- **THEN** the dependent Value totals remain unavailable without losing other metrics
|
||||
- **AND** a successfully read empty scope can still contribute zero
|
||||
|
||||
#### Scenario: Failed estimate and recovery
|
||||
- **WHEN** a valid Jupiter estimate is followed by a malformed observation
|
||||
- **THEN** the original estimate, its qualification and success/observation times remain with failure status
|
||||
- **AND** successful subsequent observations replace the estimate, including replacing it with missing Value or clearing an empty scope
|
||||
|
||||
@@ -18,11 +18,22 @@ The overview SHALL read the official positions response for one wallet, validate
|
||||
- **THEN** the successful result clears that wallet's Jupiter scope
|
||||
|
||||
### Requirement: Verified metrics and unavailable valuations
|
||||
The overview SHALL preserve size basis, use post-fee USD collateral and price PnL before separate fees, and calculate leverage and ROI against positive collateral. Unverified closing valuations SHALL be unavailable with an explanation; placeholder zeros and double subtraction of settled fees SHALL NOT be used.
|
||||
The overview SHALL preserve size basis, post-fee USD collateral and price PnL before separate fees, with leverage/ROI against positive collateral. It SHALL preserve optional reported `valueUsd` as an unverified estimate, converting integer micro-USD exactly without fee corrections. Missing/null Value SHALL remain unavailable; present malformed amounts SHALL fail the observation. Other valid metrics SHALL survive missing Value.
|
||||
|
||||
#### Scenario: Unverified API closing value
|
||||
- **WHEN** API value subtracts opening fees already settled in collateral or does not establish profit-cap valuation
|
||||
- **THEN** closing Value is unavailable, while independently verified metrics remain available
|
||||
- **WHEN** API Value subtracts opening fees already settled in collateral or does not establish profit-cap valuation
|
||||
- **THEN** the reported Value is preserved unchanged as an unverified estimate while independent metrics remain available
|
||||
- **AND** it is not represented as guaranteed closing proceeds or corrected by adding opening fees back
|
||||
|
||||
#### Scenario: Reported API closing estimate
|
||||
- **WHEN** Jupiter reports `valueUsd` as `"123234567"`, `"0"` or `"-1234567"`
|
||||
- **THEN** the overview preserves exactly 123.234567, 0 or -1.234567 USD respectively
|
||||
- **AND** it does not add opening fees back or impose a profit-cap correction
|
||||
|
||||
#### Scenario: Missing or malformed Value
|
||||
- **WHEN** `valueUsd` is missing or null
|
||||
- **THEN** Value is unavailable while independent metrics remain available
|
||||
- **AND** a present malformed amount instead fails the complete observation
|
||||
|
||||
#### Scenario: Nonpositive collateral
|
||||
- **WHEN** a position's collateral is zero or negative
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.r35157.evelyn.emc.impl.ref.PerpsRiskPresentation.Cell;
|
||||
import com.r35157.evelyn.service.perps.PerpsSnapshot.Direction;
|
||||
import com.r35157.evelyn.service.perps.PerpsSnapshot.Margin;
|
||||
import com.r35157.evelyn.service.perps.PerpsSnapshot.Metrics;
|
||||
import com.r35157.evelyn.service.perps.PerpsSnapshot.ValueEstimate;
|
||||
import com.r35157.evelyn.service.perps.PerpsSnapshot.Twap;
|
||||
import javafx.application.Platform;
|
||||
import javafx.animation.PauseTransition;
|
||||
@@ -97,13 +98,8 @@ final class PerpsTab {
|
||||
table.getColumns().add(column("Exposure", 102, 90, row -> usd(row.metrics().exposureUsd()),
|
||||
true, false, "Gross USD exposure: add absolute Long and Short notionals. Jupiter retains its "
|
||||
+ "position-size basis; Phoenix uses absolute current quantity × mark price."));
|
||||
table.getColumns().add(column("Value", 102, 90, row -> usd(row.metrics().valueUsd()),
|
||||
true, false, "Estimated net proceeds from closing at reference prices, including outstanding "
|
||||
+ "funding/borrowing and estimated exit fees where verified. Cross account Value includes "
|
||||
+ "unused shared collateral, counted once. It excludes unrelated wallet balances. "
|
||||
+ "Not an executable quote: slippage, price impact, swaps, network fees and account rent "
|
||||
+ "are not included. Cross position Value is inapplicable. "
|
||||
+ "A missing required component makes the entire affected total unavailable."));
|
||||
table.getColumns().add(column("Value", 112, 100, PerpsTab::value,
|
||||
true, false, row -> valueDefinition(row.metrics())));
|
||||
table.getColumns().add(column("uPnL", 184, 155, PerpsTab::pnl,
|
||||
true, true, "Signed unrealized price PnL before separately accounted funding/fees. "
|
||||
+ "Isolated percentage = 100 × uPnL / positive Collateral. Cross position ROI convention "
|
||||
@@ -136,6 +132,11 @@ final class PerpsTab {
|
||||
|
||||
private TreeTableColumn<PositionRow, PositionRow> column(String title, double width, double minimum,
|
||||
Function<PositionRow, String> format, boolean numeric, boolean pnl, String definition) {
|
||||
return column(title, width, minimum, format, numeric, pnl, ignored -> definition);
|
||||
}
|
||||
|
||||
private TreeTableColumn<PositionRow, PositionRow> column(String title, double width, double minimum,
|
||||
Function<PositionRow, String> format, boolean numeric, boolean pnl, Function<PositionRow, String> definition) {
|
||||
var column = new TreeTableColumn<PositionRow, PositionRow>(title);
|
||||
column.setPrefWidth(width);
|
||||
column.setMinWidth(minimum);
|
||||
@@ -150,7 +151,7 @@ final class PerpsTab {
|
||||
setTextFill(Color.web("#30343b"));
|
||||
setTooltip(null);
|
||||
if (!empty && row != null) {
|
||||
setTooltip(tooltip(definition + "\n\n" + row.label() + "\n" + row.notes()));
|
||||
setTooltip(tooltip(definition.apply(row) + "\n\n" + row.label() + "\n" + row.notes()));
|
||||
if (pnl && row.metrics().pnlUsd() != null) {
|
||||
int sign = rounded(row.metrics().pnlUsd(), 2).signum();
|
||||
setTextFill(Color.web(sign > 0 ? "#16743c" : sign < 0 ? "#b32424" : "#30343b"));
|
||||
@@ -505,6 +506,33 @@ final class PerpsTab {
|
||||
return value == null ? "—" : NumberFormat.getCurrencyInstance(Locale.US).format(rounded(value, 2));
|
||||
}
|
||||
|
||||
private static String value(PositionRow row) {
|
||||
Metrics metrics = row.metrics();
|
||||
return (metrics.valueUsd() != null && !metrics.valueEstimates().isEmpty() ? "≈" : "")
|
||||
+ usd(metrics.valueUsd());
|
||||
}
|
||||
|
||||
private static String valueDefinition(Metrics metrics) {
|
||||
String definition = "Value estimates are not executable quotes or guaranteed closing proceeds. "
|
||||
+ "Cross account Value includes unused shared collateral, counted once; Cross position Value "
|
||||
+ "is inapplicable. Unrelated wallet balances are excluded. Slippage, price impact, swaps, "
|
||||
+ "network fees and account rent are not included. "
|
||||
+ "A missing required component makes the entire affected total unavailable.";
|
||||
if (metrics.valueEstimates().contains(ValueEstimate.JUPITER)) {
|
||||
definition += "\n\nEstimated position value reported by Jupiter. Actual proceeds on closing may differ. "
|
||||
+ "The Jupiter contribution uses its reported estimate unchanged when available. An observed response subtracted "
|
||||
+ "opening fees although collateral was already recorded after fees; fee treatment is uncertain, "
|
||||
+ "and collateral valuation/profit-cap semantics are unverified. Differences are not known "
|
||||
+ "to be limited to small fees; this does not establish that every API estimate is wrong.";
|
||||
}
|
||||
if (metrics.valueEstimates().contains(ValueEstimate.PHOENIX)) {
|
||||
definition += "\n\nThe Phoenix contribution uses Phoenix estimated closing Value when available: account equity less estimated "
|
||||
+ "closing taker fees, with account fee overrides. Settled fees/funding are not deducted again. "
|
||||
+ "Actual proceeds on closing may differ.";
|
||||
}
|
||||
return definition;
|
||||
}
|
||||
|
||||
private static BigDecimal rounded(BigDecimal value, int scale) {
|
||||
BigDecimal result = value.setScale(scale, RoundingMode.HALF_UP);
|
||||
return result.signum() == 0 ? BigDecimal.ZERO.setScale(scale) : result;
|
||||
|
||||
@@ -5,8 +5,10 @@ import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
/** Immutable observations. IDs are stable among siblings; null metrics mean unavailable, never zero. */
|
||||
@@ -157,10 +159,22 @@ public record PerpsSnapshot(List<Wallet> wallets) {
|
||||
}
|
||||
}
|
||||
|
||||
/** USD monetary values; leverage is a ratio, pnlPercent is in percentage points. */
|
||||
public enum ValueEstimate { JUPITER, PHOENIX }
|
||||
|
||||
/**
|
||||
* USD amounts; leverage is a ratio, pnlPercent is in percentage points.
|
||||
* Estimate sources survive incomplete totals; only available Value gets a marker.
|
||||
*/
|
||||
public record Metrics(ΩAmountΩ collateralUsd, BigDecimal leverage, ΩAmountΩ exposureUsd,
|
||||
ΩAmountΩ valueUsd, ΩAmountΩ pnlUsd, BigDecimal pnlPercent,
|
||||
Set<ValueEstimate> valueEstimates) {
|
||||
public Metrics(ΩAmountΩ collateralUsd, BigDecimal leverage, ΩAmountΩ exposureUsd,
|
||||
ΩAmountΩ valueUsd, ΩAmountΩ pnlUsd, BigDecimal pnlPercent) {
|
||||
this(collateralUsd, leverage, exposureUsd, valueUsd, pnlUsd, pnlPercent, Set.of());
|
||||
}
|
||||
|
||||
public Metrics {
|
||||
valueEstimates = Set.copyOf(valueEstimates);
|
||||
if (exposureUsd != null && exposureUsd.signum() < 0) {
|
||||
throw new IllegalArgumentException("Gross exposure must not be negative");
|
||||
}
|
||||
@@ -171,9 +185,11 @@ public record PerpsSnapshot(List<Wallet> wallets) {
|
||||
}
|
||||
|
||||
public static Metrics total(List<Metrics> components) {
|
||||
var estimates = EnumSet.noneOf(ValueEstimate.class);
|
||||
components.forEach(component -> estimates.addAll(component.valueEstimates()));
|
||||
return new Metrics(sum(components, Metrics::collateralUsd), null,
|
||||
sum(components, Metrics::exposureUsd), sum(components, Metrics::valueUsd),
|
||||
sum(components, Metrics::pnlUsd), null);
|
||||
sum(components, Metrics::pnlUsd), null, estimates);
|
||||
}
|
||||
|
||||
private static ΩAmountΩ sum(List<Metrics> components, Function<Metrics, BigDecimal> field) {
|
||||
|
||||
+8
-7
@@ -9,6 +9,7 @@ import com.r35157.evelyn.service.perps.PerpsSnapshot.Wallet;
|
||||
import com.r35157.evelyn.service.perps.PerpsSnapshot.Direction;
|
||||
import com.r35157.evelyn.service.perps.PerpsSnapshot.Margin;
|
||||
import com.r35157.evelyn.service.perps.PerpsSnapshot.Metrics;
|
||||
import com.r35157.evelyn.service.perps.PerpsSnapshot.ValueEstimate;
|
||||
import com.r35157.evelyn.service.perps.PerpsSnapshot.CrossAccount;
|
||||
import com.r35157.libs.codec.impl.ref.Base58CodecImpl;
|
||||
import com.r35157.libs.jupiter.perps.JupiterPerpsOverviewPosition;
|
||||
@@ -31,6 +32,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -134,7 +136,7 @@ public final class EvelynPerpsServiceImpl implements EvelynPerpsService, NenjimP
|
||||
cross.add(new CrossAccount("cross:" + account.traderPdaIndex() + ":" + account.subaccountIndex(),
|
||||
"Cross", new Metrics(account.collateralUsd(),
|
||||
PerpsSnapshot.ratio(sum.exposureUsd(), account.equityUsd()), sum.exposureUsd(),
|
||||
account.valueUsd(), sum.pnlUsd(), null), positions, PHOENIX_ACCOUNT_NOTES,
|
||||
account.valueUsd(), sum.pnlUsd(), null, Set.of(ValueEstimate.PHOENIX)), positions, PHOENIX_ACCOUNT_NOTES,
|
||||
risk(account, null), warnings(account, null)));
|
||||
}
|
||||
} else {
|
||||
@@ -160,7 +162,7 @@ public final class EvelynPerpsServiceImpl implements EvelynPerpsService, NenjimP
|
||||
return new Position(id, instrument, direction, cross ? Margin.CROSS : Margin.ISOLATED,
|
||||
new Metrics(collateral, cross ? null : PerpsSnapshot.ratio(position.sizeUsd(), collateral),
|
||||
position.sizeUsd(), cross ? null : account.valueUsd(), position.pnlUsd(),
|
||||
cross ? null : PerpsSnapshot.percentage(position.pnlUsd(), collateral)),
|
||||
cross ? null : PerpsSnapshot.percentage(position.pnlUsd(), collateral), Set.of(ValueEstimate.PHOENIX)),
|
||||
cross ? PHOENIX_CROSS_NOTES : PHOENIX_ACCOUNT_NOTES, twap,
|
||||
cross ? null : risk(account, position), warnings(account, position.symbol()));
|
||||
}
|
||||
@@ -202,8 +204,8 @@ public final class EvelynPerpsServiceImpl implements EvelynPerpsService, NenjimP
|
||||
return new Position(position.positionAccount(), instrument(position.tradedTokenMint()),
|
||||
direction, Margin.ISOLATED,
|
||||
new Metrics(position.collateralUsd(), PerpsSnapshot.ratio(position.sizeUsd(), position.collateralUsd()),
|
||||
position.sizeUsd(), null, position.pnlUsd(),
|
||||
PerpsSnapshot.percentage(position.pnlUsd(), position.collateralUsd())),
|
||||
position.sizeUsd(), position.valueUsd(), position.pnlUsd(),
|
||||
PerpsSnapshot.percentage(position.pnlUsd(), position.collateralUsd()), Set.of(ValueEstimate.JUPITER)),
|
||||
JUPITER_NOTES, null, new PerpsSnapshot.Risk(null, null, position.markPriceUsd(),
|
||||
position.liquidationPriceUsd(), null, null, position.observedAt()));
|
||||
}
|
||||
@@ -239,9 +241,8 @@ public final class EvelynPerpsServiceImpl implements EvelynPerpsService, NenjimP
|
||||
private static final long REFRESH_SECONDS = 60;
|
||||
private final List<PerpsWalletConfiguration> wallets;
|
||||
private static final String JUPITER_NOTES = "Jupiter: collateral is recorded post-fee USD collateral; "
|
||||
+ "uPnL is price PnL before separate fees. Value unavailable: the API's valueUsd also subtracts "
|
||||
+ "opening fees already reflected in collateral; collateral valuation and profit-cap treatment "
|
||||
+ "are not verified. A missing metric is not zero.";
|
||||
+ "uPnL is price PnL before separate fees. Value, when reported, is Jupiter's uncorrected estimate, "
|
||||
+ "not guaranteed closing proceeds. A missing metric is not zero.";
|
||||
private static final String PHOENIX_ACCOUNT_NOTES = "Phoenix: account equity = actual collateral + "
|
||||
+ "undiscounted price uPnL + signed unsettled funding. Value estimates equity less closing "
|
||||
+ "taker fees with the account fee override; settled fees/funding are not deducted again. "
|
||||
|
||||
@@ -4,11 +4,20 @@ import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Monetary amounts are USD, not raw micro-USD; missing metrics are null. No unverified API net Value. */
|
||||
/** USD amounts, not raw micro-USD; null means unavailable. Value is the venue's unverified estimate. */
|
||||
public record JupiterPerpsOverviewPosition(ΩJupiterPerpsPositionAccountΩ positionAccount,
|
||||
ΩSPLMintAddressΩ tradedTokenMint, JupiterPerpsPositionDirection direction,
|
||||
ΩAmountΩ sizeUsd, ΩAmountΩ collateralUsd, ΩAmountΩ pnlUsd,
|
||||
ΩPriceΩ markPriceUsd, ΩPriceΩ liquidationPriceUsd, Instant observedAt,
|
||||
ΩAmountΩ valueUsd) {
|
||||
public JupiterPerpsOverviewPosition(ΩJupiterPerpsPositionAccountΩ positionAccount,
|
||||
ΩSPLMintAddressΩ tradedTokenMint, JupiterPerpsPositionDirection direction,
|
||||
ΩAmountΩ sizeUsd, ΩAmountΩ collateralUsd, ΩAmountΩ pnlUsd,
|
||||
ΩPriceΩ markPriceUsd, ΩPriceΩ liquidationPriceUsd, Instant observedAt) {
|
||||
this(positionAccount, tradedTokenMint, direction, sizeUsd, collateralUsd, pnlUsd,
|
||||
markPriceUsd, liquidationPriceUsd, observedAt, null);
|
||||
}
|
||||
|
||||
public JupiterPerpsOverviewPosition(ΩJupiterPerpsPositionAccountΩ positionAccount,
|
||||
ΩSPLMintAddressΩ tradedTokenMint, JupiterPerpsPositionDirection direction,
|
||||
ΩAmountΩ sizeUsd, ΩAmountΩ collateralUsd, ΩAmountΩ pnlUsd) {
|
||||
|
||||
+2
-1
@@ -87,7 +87,8 @@ public final class RestJupiterPerpsOverview implements JupiterPerpsOverview {
|
||||
}
|
||||
positions.add(new JupiterPerpsOverviewPosition(id, mint, direction, size,
|
||||
micros(row, "collateralUsd"), micros(row, "pnlBeforeFeesUsd"),
|
||||
positive(micros(row, "markPriceUsd")), positive(micros(row, "liquidationPriceUsd")), observedAt));
|
||||
positive(micros(row, "markPriceUsd")), positive(micros(row, "liquidationPriceUsd")), observedAt,
|
||||
micros(row, "valueUsd")));
|
||||
}
|
||||
positions.sort(Comparator.comparing(JupiterPerpsOverviewPosition::positionAccount));
|
||||
return List.copyOf(positions);
|
||||
|
||||
Reference in New Issue
Block a user