8.5 KiB
evelyn-perps-service Specification
Purpose
Provide a view-only source of current Evelyn perpetual positions independently of Mission Control presentation and refresh timing.
Requirements
Requirement: Read-only current positions snapshot
The Perps service SHALL expose only a read operation returning its complete current in-memory snapshot. Wallets, venues within wallets, and positions within venues SHALL have stable identifiers across reads. Responses SHALL be immutable domain data without UI components, colors or formatting. Reading the snapshot SHALL make no Jupiter or Phoenix requests.
Scenario: Repeated reads
- WHEN clients repeatedly retrieve the started service's snapshot
- THEN they receive the latest published positions with stable identifiers and cannot mutate service-owned collections
- AND the reads trigger no exchange requests or position-management operations
Requirement: Hub owns Perps startup
The service SHALL run as two separate Hub-managed processes registered as evelyn.perps.test and evelyn.perps.prod. The service API SHALL expose no lifecycle operations. Snapshot reads before process startup SHALL fail clearly. Startup SHALL schedule asynchronous retrieval without waiting for integration I/O.
Scenario: Service starts
- WHEN the Hub starts either registered Perps process
- THEN its service view is available without waiting for retrieval, initially empty until data arrives
- AND no placeholder positions are generated before successful retrieval
Requirement: Wallet configuration belongs to service construction
The Hub SHALL supply the six PROD wallet addresses and one TEST wallet address specified in issue #98, a read-only Jupiter overview reader and a read-only Phoenix integration at construction. Each service SHALL retain immutable configuration and own its snapshot. Wallet addresses SHALL be used as stable identities and display labels. EMC SHALL neither own nor pass wallet configuration; reads SHALL remain parameterless. Existing Jupiter trading/alarm services SHALL retain their behavior.
Scenario: Separate configured instances
- WHEN the Hub constructs and starts both instances
- THEN each instance queries only its configured wallets through its injected integrations
- AND the implementation does not define the wallet list internally
Requirement: Background Jupiter refresh
The service SHALL refresh immediately after startup and wait 60 seconds after completed cycles without overlap or dependence on EMC visibility. It SHALL use a dedicated read-only Jupiter overview and the Phoenix account reader. Snapshot reads SHALL make no integration calls. No signing or position-management operation SHALL occur; existing transaction/alarm reads SHALL retain their behavior.
Scenario: Background update while EMC is hidden
- WHEN EMC is hidden and the next refresh is due
- THEN the service refreshes configured wallets and publishes immutable snapshots
- AND all UI snapshot reads remain in-memory
Requirement: Position mapping is stable and complete
The service SHALL preserve Jupiter position-account identities and its existing USD position-size basis. Phoenix SHALL preserve PDA/subaccount/symbol identities and absolute current notional. Isolated positions SHALL remain directly under venues; Cross accounts SHALL own their positions and shared balances separately. Wallet and position ordering SHALL be deterministic. No account membership SHALL be inferred from instrument labels.
Scenario: Same-market cross and isolated positions
- WHEN a Phoenix wallet holds SOL in Cross and an isolated subaccount
- THEN both positions retain distinct IDs and their actual account membership
Scenario: Changed and unknown positions
- WHEN a successful read returns newly opened positions including an unknown token mint
- THEN all returned positions appear with stable IDs and exact USD sizes, and the unknown mint is displayed rather than dropped
Requirement: Refresh failures are isolated per wallet
A failed read or mapping SHALL retain the last successful data for that wallet and venue, log the failure and allow other wallets and venues to refresh. Successful empty results SHALL remove only that wallet/venue's previous positions. Interruption SHALL stop the current cycle. Failed first reads SHALL not invent positions.
Scenario: Failure followed by recovery
- WHEN one wallet fails while another succeeds
- THEN only the successful wallet/venue's data is replaced
- AND a later successful empty read clears the recovered wallet/venue's previous positions
Scenario: One venue fails
- WHEN Phoenix fails for a wallet while Jupiter succeeds, or vice versa
- THEN only the successful venue is updated and the failed venue retains its previous data
- AND subsequent wallets are still attempted
Requirement: Complete gross and account-aware totals
Exposure and dollar price PnL SHALL sum through all levels. Exposure SHALL be gross, with positive absolute notionals. Capital and closing Value SHALL count isolated positions and shared Cross accounts once. Missing required components SHALL make only the affected total unavailable. Successfully read empty scopes SHALL total zero. Leverage and PnL percentages SHALL NOT be aggregated.
Scenario: Opposite directions and shared collateral
- WHEN two Cross positions have notionals 100 USD Long and 100 USD Short, shared collateral 80 USD, combined PnL 5 USD and closing fees 1 USD
- THEN exposure totals 200 USD, collateral 80 USD and Value 84 USD at account, venue, wallet and root levels
- AND collapsing any UI group does not change totals
Scenario: First read fails
- WHEN one configured venue has never been read successfully
- THEN it remains unknown and cannot silently produce complete portfolio totals
Requirement: Retained observations communicate status
Each venue SHALL carry its last-success time and whether the latest attempt failed. Failure SHALL preserve positions and account balances independently per wallet/venue. A valid funded empty Cross account SHALL remain visible. Retained data SHALL NOT be described as fresh.
Scenario: Last Cross position closes
- WHEN a successful refresh returns a Cross account with nonzero collateral and no positions
- THEN its capital and Value remain included without synthetic positions
Scenario: Retained last success
- WHEN a subsequent read fails
- THEN its previous observations remain available with their original success time and failure indication
Requirement: TWAP observations belong to existing filled positions
The service SHALL publish immutable TWAP observations only on matching existing Phoenix positions whose order has a positive actual fill and remaining executions. It SHALL preserve counters, quantities, nullable price and observation/estimated times without UI formatting. No new position SHALL be synthesized. TWAP errors SHALL retain the complete prior venue observation with failure status. Polling frequency SHALL remain unchanged.
Scenario: Unfilled order or no position
- WHEN an active TWAP has no actual fills or has no matching open position
- THEN it is absent from every overview row and cannot create a visible venue
Scenario: TWAP read fails
- WHEN the TWAP read fails after a successful venue observation
- THEN prior positions, TWAP observations and original timestamps remain with failed venue status
- AND other venues and wallets continue refreshing
Requirement: Risk observations preserve scope and exact meaning
Snapshots SHALL preserve isolated risk prices and Cross account tiers/thresholds with original observation times. Signed price distance SHALL use the current venue reference as denominator and preserve boundary crossing. Cross MM utilization SHALL use effective collateral, with nonpositive denominators unavailable. Group risk SHALL count each isolated position and shared account once and retain worst known severity plus unknown/stale counts without averaging.
Scenario: Shared positions and incomplete data
- WHEN a Cross account owns multiple positions and another scope has unknown risk
- THEN account risk contributes once and the aggregate exposes the unknown count
- AND its closing Value is not substituted for effective collateral
Scenario: Boundary and zero denominator
- 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