96: Extract Perps sample data into EvelynPerpsService and poll it from EMC
This commit is contained in:
@@ -431,3 +431,23 @@ minuts fast forsinkelse, og et punkt persisteres før publicering i et immutable
|
|||||||
hukommelsen og beholder navnereservationen, restart genindlæser filen, og `close()` frigiver navnet efter sikker
|
hukommelsen og beholder navnereservationen, restart genindlæser filen, og `close()` frigiver navnet efter sikker
|
||||||
terminering. `EvelynStatusIndexPoint` bliver i sin eksisterende package med alle seks indeksfelter; kun Evelyn
|
terminering. `EvelynStatusIndexPoint` bliver i sin eksisterende package med alle seks indeksfelter; kun Evelyn
|
||||||
Price Index beregnes og vises, mens de øvrige fem felter fortsat er nul. Legacy-rebalancering er fortsat inaktiv.
|
Price Index beregnes og vises, mens de øvrige fem felter fortsat er nul. Legacy-rebalancering er fortsat inaktiv.
|
||||||
|
|
||||||
|
## 12. Perps-service og Mission Control
|
||||||
|
|
||||||
|
`com.r35157.evelyn.service.perps.EvelynPerpsService` ejer positionsdata og udleverer hele det aktuelle,
|
||||||
|
immutable `PerpsSnapshot` gennem `getPositionsSnapshot()`. Snapshot'et indeholder wallets, deres venues og
|
||||||
|
positioner med stabile ID'er blandt søskende, adskilt fra visningsnavne. Positioner indeholder instrument,
|
||||||
|
`LONG`/`SHORT` og decimal USD-størrelse. Svaret indeholder ingen JavaFX-elementer, farver eller formatering.
|
||||||
|
|
||||||
|
`impl.ref.EvelynPerpsServiceImpl` implementerer desuden `NenjimProcess`. Hub'en registrerer og starter det samme
|
||||||
|
objekt som `evelyn.perps.default` før EMC. Før `start()` afvises læsning; gentagen start afvises også.
|
||||||
|
Service-API'et eksponerer kun læsning, ingen lifecycle- eller positionsændringer. Servicen ejer de otte
|
||||||
|
uændrede eksempelpositioner fra den oprindelige Perps-fane og har ingen workers eller Jupiter/Phoenix-kald.
|
||||||
|
|
||||||
|
EMC læser snapshot'et straks ved åbning og hvert tredje sekund på en separat worker, uafhængigt af en eventuel
|
||||||
|
senere integrations opdateringsinterval. Begge miljøfaner viser fortsat de samme tydeligt markerede eksempler;
|
||||||
|
EMC ejer træstruktur, USD-formatering og grønne/røde rækker. Uændrede snapshots berører ikke eksisterende rækker,
|
||||||
|
foldestatus eller markering. Ved ændringer genbruges træelementer efter deres ID'er inden for samme forælder.
|
||||||
|
UI-opdateringer sker kun på JavaFX-tråden. Fejl bevarer visningen og stopper ikke næste pollingforsøg.
|
||||||
|
Skjul/luk stopper polling; genåbning starter én ny løkke og afviser gamle resultater. Servicen fortsætter uafhængigt
|
||||||
|
af vinduet. Der er ingen rigtig integration, simulerede ændringer, Refresh-knap eller handel i denne service.
|
||||||
|
|||||||
@@ -271,6 +271,7 @@ jupiter-perps-alarm.default
|
|||||||
evelyn.status.prod
|
evelyn.status.prod
|
||||||
evelyn.status.test
|
evelyn.status.test
|
||||||
evelyn.iou-burner.prod
|
evelyn.iou-burner.prod
|
||||||
|
evelyn.perps.default
|
||||||
nenjim.javafx-runtime.service
|
nenjim.javafx-runtime.service
|
||||||
evelyn.mission-control.default
|
evelyn.mission-control.default
|
||||||
nenjim.process-manager.default
|
nenjim.process-manager.default
|
||||||
@@ -283,6 +284,13 @@ navngivne `Production`- og `Test`-instanser gennem `EvelynStatusServiceImpl` og
|
|||||||
`EvelynStatusService`-views i Mission Control. Process Manager viser de nye ID'er fra Hub'ens startup-snapshot.
|
`EvelynStatusService`-views i Mission Control. Process Manager viser de nye ID'er fra Hub'ens startup-snapshot.
|
||||||
Historik ligger fortsat i `data/evelyn/<instance-name>/status.log` med formatversion 1 og kræver ingen migration.
|
Historik ligger fortsat i `data/evelyn/<instance-name>/status.log` med formatversion 1 og kræver ingen migration.
|
||||||
|
|
||||||
|
Mission Control kræver også `evelyn.perps.default` før `evelyn.mission-control.default` i den private
|
||||||
|
`conf/NenjimHub.conf`. Hub'en konstruerer, registrerer og starter `EvelynPerpsServiceImpl` som en separat
|
||||||
|
proces og injicerer samme objekts read-only `EvelynPerpsService`-view i EMC. Process Manager viser ID'et efter
|
||||||
|
vellykket startup. Den lokale udviklingskonfiguration vælger nu servicen før EMC; andre deployments med EMC
|
||||||
|
skal tilføje samme linje. Udeladt eller forkert rækkefølge giver en tydelig konstruktionsfejl uden automatisk
|
||||||
|
indsættelse. Servicen bruger kun uændrede in-memory-eksempler og foretager ingen eksterne positionskald.
|
||||||
|
|
||||||
Hvis `evelyn.iou-burner.prod` vælges, skal både `assetaz.ticker.default` og
|
Hvis `evelyn.iou-burner.prod` vælges, skal både `assetaz.ticker.default` og
|
||||||
`evelyn.status.prod` stå før den. Hub'en injicerer Production status-servicen til
|
`evelyn.status.prod` stå før den. Hub'en injicerer Production status-servicen til
|
||||||
referenceprisopslag før nye buybacks; en manglende eller senere status-forgænger
|
referenceprisopslag før nye buybacks; en manglende eller senere status-forgænger
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-10-19
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Design
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
See proposal.md for motivation. `PerpsTab` currently builds all sample rows. EMC uses a JavaFX Timeline for chart refresh and stops it on hide. The Hub explicitly constructs, registers, starts and records selected processes in configuration order.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:** Separate domain data from presentation; preserve UI identity; make polling independent of any future integration cadence.
|
||||||
|
|
||||||
|
**Non-Goals:** Exchange dependencies, a data-update worker, trading, changing samples, additional UI controls or a new general lifecycle framework.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
- Put `EvelynPerpsService`, snapshot records and direction in `com.r35157.evelyn.service.perps`, with implementation in `impl.ref`. Nested wallet/venue/position records use IDs separate from display labels and defensive list copies. Use the existing Amount ValueTag for decimal USD size. UI labels/colors remain in EMC. A UI-shaped response would couple the service to JavaFX; a flat response would duplicate grouping responsibility.
|
||||||
|
- Use one `evelyn.perps.default` process for the unchanged examples shown in both environments. Its start publishes the immutable snapshot, reads before start fail, and a second start fails. No worker is needed for static in-memory data. Separate prod/test instances would add configuration without a current difference in data.
|
||||||
|
- Use a single daemon scheduled worker per visible EMC session, requesting immediately and every three seconds. Reads execute sequentially off JavaFX; completion is dispatched through the injected JavaFX runtime. Hide cancels the worker, and a session token rejects queued obsolete results after reopening. Exceptions are logged and isolated so polling continues. Reuse the existing chart lifecycle unchanged.
|
||||||
|
- Retain a Perps view per environment. Skip equal snapshots completely. Reconcile wallet/venue/position tree items by IDs within their parent, updating only changed row values and child order, preserving retained expansion and selection. Do not rebuild or call table refresh for unchanged data.
|
||||||
|
- Add the service to the Hub catalogue and require it in EMC construction. Update the ignored deployment configuration before EMC; retain the minimal tracked bootstrap example and document the migration.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- Late results after hide → cancel the session worker and check its identity on the JavaFX thread before applying.
|
||||||
|
- A slow read → sequential worker prevents overlapping polling calls; current snapshot reads have no I/O.
|
||||||
|
- Runtime configuration is ignored → document the required ID and order for other deployments.
|
||||||
|
- Static samples cannot prove real integration correctness → verify only the requested boundary, appearance and polling behavior.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
Build and validate offline, then run an isolated JavaFX verification without starting the live Hub or financial services. On the next ordinary Hub startup, the modified local autostart list starts Perps before EMC. Other deployments must add the same entry explicitly. Rollback restores the previous code and removes that entry together. Leave the change ready for review, without commit, push, deployment or automatic archive.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Proposal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Issue #96 moves ownership of Evelyn's existing Perps sample positions out of EMC into a view-only service. This establishes the data boundary for Evelyn's position overview while preserving its current appearance and avoiding exchange integration work.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Introduce `EvelynPerpsService` with an immutable, consistently identified wallet → venue → position snapshot and a separate reference implementation using the Hub process lifecycle.
|
||||||
|
- Move all existing hardcoded positions into that service, without network access or position-management operations.
|
||||||
|
- Poll the snapshot off the JavaFX thread every three seconds while EMC is visible; retain tree items, expansion and selection when data is unchanged.
|
||||||
|
- Register `evelyn.perps.default`, require it before EMC, and add it to this deployment's ordered autostart configuration.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `evelyn-perps-service`: View-only, in-memory Perps snapshots with stable domain identifiers and Hub-owned startup.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- `evelyn-perps-overview`: Replace EMC-owned samples with asynchronous lifecycle-bound polling while retaining presentation and view state.
|
||||||
|
- `nenjim-hub-configuration`: Support the Perps process and explicitly ordered injection into EMC.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
Changes the Evelyn service API, EMC constructor and Perps presentation, Hub composition, local ignored runtime configuration, and documentation. No external dependencies, trades, real exchange reads, simulated changes, new controls, commits or pushes.
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
# Spec Delta
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Prototype is clearly labeled and entirely local
|
||||||
|
The Perps view SHALL explicitly label its data as sample data and explain that Phoenix rows are illustrative without an existing integration. It SHALL obtain positions only from the Perps service's current in-memory snapshot, with no external network calls, Refresh button, or simulated position changes. EMC SHALL own all UI components, row colors and formatting.
|
||||||
|
|
||||||
|
#### Scenario: Inspect the prototype boundary
|
||||||
|
- **WHEN** the Perps tab is displayed
|
||||||
|
- **THEN** sample-data and illustrative-Phoenix labels are visible
|
||||||
|
- **AND** there is no Refresh button or simulated update of sample positions
|
||||||
|
- **AND** displaying and interacting with the prototype uses only the service's local sample snapshot
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Asynchronous polling follows window visibility
|
||||||
|
EMC SHALL request the current Perps snapshot immediately on opening and every three seconds while visible, without blocking the UI thread. UI updates SHALL occur only on the JavaFX thread. Hiding the window SHALL stop polling; reopening SHALL create only one active polling loop and ignore results belonging to an earlier window session. Poll failures SHALL preserve the current view and allow subsequent attempts.
|
||||||
|
|
||||||
|
#### Scenario: Read takes time or fails
|
||||||
|
- **WHEN** a snapshot read is delayed or fails
|
||||||
|
- **THEN** the UI remains responsive and the existing view is retained
|
||||||
|
- **AND** later polling remains possible without overlapping scheduled reads
|
||||||
|
|
||||||
|
#### Scenario: Hide and reopen
|
||||||
|
- **WHEN** the owner hides and reopens EMC repeatedly
|
||||||
|
- **THEN** each visible session has only one polling loop and obsolete results cannot update its views
|
||||||
|
|
||||||
|
### Requirement: Polling preserves Perps view state
|
||||||
|
The Perps table SHALL retain matching wallet, venue and position identities across snapshots. Repeated unchanged snapshots SHALL leave existing rows, expansion, selection and formatting untouched without flicker. New groups SHALL be expanded by default, and future added or removed positions SHALL be reconciled by identity without resetting retained groups.
|
||||||
|
|
||||||
|
#### Scenario: Poll unchanged positions
|
||||||
|
- **WHEN** a user collapses a group or selects a position and repeated snapshots are unchanged
|
||||||
|
- **THEN** the same rows remain, with the same expansion and selection and no rebuild or refresh
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
# Spec Delta
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Provide a view-only source of current Evelyn perpetual positions independently of Mission Control presentation and refresh timing.
|
||||||
|
|
||||||
|
## ADDED 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, and SHALL make no Jupiter or Phoenix requests.
|
||||||
|
|
||||||
|
#### Scenario: Repeated reads
|
||||||
|
- **WHEN** clients repeatedly retrieve the started service's snapshot
|
||||||
|
- **THEN** they receive the same complete positions with stable identifiers and cannot mutate service-owned collections
|
||||||
|
- **AND** no exchange requests or position-management operations occur
|
||||||
|
|
||||||
|
### Requirement: Service owns the unchanged sample positions
|
||||||
|
The service SHALL own the existing eight sample positions grouped under Sample wallet A and Sample wallet B and their Jupiter and Phoenix venues, retaining every existing instrument, direction and decimal USD size. The snapshot SHALL remain unchanged for this task.
|
||||||
|
|
||||||
|
#### Scenario: Existing samples are retrieved
|
||||||
|
- **WHEN** EMC retrieves the service snapshot
|
||||||
|
- **THEN** wallet A contains Jupiter SOL Long 12500.00 and BTC Short 8250.50, Phoenix SOL Short 3200.75 and ETH Long 5400.00
|
||||||
|
- **AND** wallet B contains Jupiter ETH Long 1875.25 and SOL Short 950.00, Phoenix BTC Long 21000.00 and SOL Short 640.50
|
||||||
|
|
||||||
|
### Requirement: Hub owns Perps startup
|
||||||
|
The service SHALL run as a separate Hub-managed process registered as `evelyn.perps.default`. The service API SHALL expose no lifecycle operations. Snapshot reads before process startup SHALL fail clearly.
|
||||||
|
|
||||||
|
#### Scenario: Service starts
|
||||||
|
- **WHEN** the Hub starts the registered Perps process
|
||||||
|
- **THEN** its service view makes the complete sample snapshot available without starting a polling or integration worker
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
# Spec Delta
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Selected processes are constructed and started lazily in exact order
|
||||||
|
The Hub SHALL support the modern process IDs `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `jupiter-perps-alarm.default`, `evelyn.status.prod`, `evelyn.status.test`, `evelyn.iou-burner.prod`, `evelyn.liquidity.prod`, `evelyn.perps.default`, `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default` through implementation-only construction capability that creates no process instance during validation. After passive composition, the Hub SHALL visit every configured entry after Registry in declared order, beginning with Journal. At each entry it SHALL construct only that process and any explicitly composed process-specific passive dependencies or administration view, register the process under the exact configured ID, resolve it through the canonical `NenjimProcess` contract where applicable, call `start()`, and record the ID only after successful return.
|
||||||
|
|
||||||
|
The production IOU burner SHALL receive the already constructed Production Evelyn status service, and SHALL require both Ticker and `evelyn.status.prod` to precede `evelyn.iou-burner.prod`. EMC SHALL receive the already started `evelyn.perps.default` service, which SHALL precede EMC in the deployment autostart list. The configuration SHALL be the complete manually ordered process dependency closure. The Hub SHALL not discover, insert, recursively construct, reorder, or automatically start dependencies. A process omitted from `[AUTO_START]` SHALL not be constructed, registered, started, or recorded, even when its implementation is available. Legacy process prototypes outside the modern Hub graph SHALL not be selectable.
|
||||||
|
|
||||||
|
#### Scenario: Configured process starts successfully
|
||||||
|
- **WHEN** a configured process entry is reached and its dependencies are already available
|
||||||
|
- **THEN** that process is constructed, registered, started, and recorded before the next entry is reached
|
||||||
|
|
||||||
|
#### Scenario: Process is omitted
|
||||||
|
- **WHEN** a known process ID does not appear in `[AUTO_START]`
|
||||||
|
- **THEN** no instance of that process is constructed or registered and its lifecycle is never started or recorded
|
||||||
|
|
||||||
|
#### Scenario: Jupiter Perps Alarm is selected
|
||||||
|
- **WHEN** `jupiter-perps-alarm.default` is explicitly listed after its required predecessors
|
||||||
|
- **THEN** the otherwise inactive modern alarm process is constructed, registered, started and recorded at that exact position
|
||||||
|
|
||||||
|
#### Scenario: Dependency order is invalid
|
||||||
|
- **WHEN** a selected process is reached before a process dependency required by its current constructor or startup behavior
|
||||||
|
- **THEN** construction or startup fails clearly without implicit dependency construction, repair, or reordering
|
||||||
|
|
||||||
|
#### Scenario: JavaFX processes are omitted
|
||||||
|
- **WHEN** a valid headless plan omits `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default`
|
||||||
|
- **THEN** none of those objects is constructed or registered and the JavaFX toolkit remains uninitialized by the Hub
|
||||||
|
|
||||||
|
#### Scenario: Renamed status services retain dependency wiring
|
||||||
|
- **WHEN** `evelyn.status.prod` and `evelyn.status.test` are listed after Ticker and before Mission Control
|
||||||
|
- **THEN** the Hub constructs separate Production/Test status services, registers the renamed IDs, and injects the corresponding typed service views into Mission Control in the configured order
|
||||||
|
|
||||||
|
#### Scenario: Deployment still contains an old status ID
|
||||||
|
- **WHEN** a private deployment configuration contains `evelyn.service.prod` or `evelyn.service.test`
|
||||||
|
- **THEN** validation rejects the unknown ID before startup; deployment migration replaces only that ID with `evelyn.status.prod` or `evelyn.status.test`, preserving selection and order
|
||||||
|
|
||||||
|
#### Scenario: Liquidity is explicitly selected
|
||||||
|
- **WHEN** `evelyn.liquidity.prod` is reached in a valid startup plan
|
||||||
|
- **THEN** the Hub loads its runtime configuration, binds the dedicated signer/wallet and Jupiter service to each other, constructs direct Raydium access and registers/starts the liquidity process without inserting a Ticker or status dependency or reordering the plan
|
||||||
|
|
||||||
|
#### Scenario: Production burner has its reference-price dependency
|
||||||
|
- **WHEN** `evelyn.iou-burner.prod` is reached after Ticker and `evelyn.status.prod`
|
||||||
|
- **THEN** the Hub injects the corresponding Production status service for buyback price decisions
|
||||||
|
|
||||||
|
#### Scenario: Production burner precedes its status dependency
|
||||||
|
- **WHEN** the burner is reached without a previously constructed Production status service
|
||||||
|
- **THEN** construction fails clearly without creating or starting the omitted dependency or the burner
|
||||||
|
|
||||||
|
#### Scenario: EMC has its Perps dependency
|
||||||
|
- **WHEN** `evelyn.perps.default` appears before EMC in the deployment's ordered autostart list
|
||||||
|
- **THEN** the Hub registers and starts that service and injects its service view into EMC
|
||||||
|
|
||||||
|
#### Scenario: EMC precedes Perps
|
||||||
|
- **WHEN** EMC is selected without an earlier Perps process
|
||||||
|
- **THEN** construction fails clearly without inserting or starting that dependency
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Tasks
|
||||||
|
|
||||||
|
## 1. Perps service and data boundary
|
||||||
|
|
||||||
|
- [x] 1.1 Add the domain-only immutable snapshot API and Hub-started reference service; verify all eight existing values, stable IDs, read-only API and defensive list copies by source inspection and compilation.
|
||||||
|
- [x] 1.2 Document ownership and lifecycle in docs/Evelyn.md; verify documentation matches the public API and no exchange dependencies are introduced.
|
||||||
|
|
||||||
|
## 2. EMC polling and presentation
|
||||||
|
|
||||||
|
- [x] 2.1 Replace EMC-owned samples with identity-based snapshot rendering; verify unchanged snapshots leave tree items, expansion and selection untouched, and formatting/colors remain unchanged.
|
||||||
|
- [x] 2.2 Inject the service and add sequential off-thread three-second polling tied to window visibility; verify initial retrieval, error recovery, hide/reopen cancellation and obsolete-result rejection in an isolated JavaFX session.
|
||||||
|
|
||||||
|
## 3. Hub startup wiring
|
||||||
|
|
||||||
|
- [x] 3.1 Add the process catalogue entry and required EMC dependency, and update local autostart before EMC; verify actual configuration parses and the same service object has both service/process views.
|
||||||
|
- [x] 3.2 Document the new process ID and explicit ordering in docs/Nenjim.md; verify the tracked minimal bootstrap example remains unchanged.
|
||||||
|
|
||||||
|
## 4. Integration verification
|
||||||
|
|
||||||
|
- [x] 4.1 Run the normal Gradle build, strict OpenSpec validation and diff review; record results and leave all work uncommitted and unpushed with the active change ready for review.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Verification
|
||||||
|
|
||||||
|
Verified 2026-10-19 against Gitea issue #96 and the owner's explicit autostart requirement.
|
||||||
|
|
||||||
|
## Build and specification checks
|
||||||
|
|
||||||
|
- `JAVA_HOME=/usr/local/software/java/jfx-25 ./gradlew build` passed, including normal Detag processing, compilation, packaging and the existing 42 tests across six suites, with zero failures, errors or skips.
|
||||||
|
- `openspec validate 96-extract-evelyn-perps-service --strict` passed.
|
||||||
|
- `git diff --check` passed.
|
||||||
|
- No automated tests were added to the repository. The runtime checks below used a temporary standalone preview under `/tmp/emc96-preview`.
|
||||||
|
|
||||||
|
## Service and Hub verification
|
||||||
|
|
||||||
|
- Snapshot reads before startup and duplicate startup fail clearly. The started service returns its complete fixed immutable snapshot; wallet, venue and position collections reject mutation.
|
||||||
|
- Inspected all eight original instruments, directions and decimal USD values and stable sibling IDs. The public service API has only one snapshot read operation. Its response types contain no JavaFX, presentation formatting or exchange dependencies.
|
||||||
|
- Parsed the actual ignored `conf/NenjimHub.conf` against the Hub's constructor catalogue. It includes `evelyn.perps.default` before EMC.
|
||||||
|
- Exercised the Hub's normal `constructRegisterAndStartProcess` path for only Perps with an isolated Registry. The same object resolved as both `EvelynPerpsService` and `NenjimProcess`, returned a populated snapshot and appeared in the Hub's successful-start snapshot.
|
||||||
|
- The tracked `conf/NenjimHub.conf.example` is unchanged. No operational Hub, wallet signer, financial process or exchange integration was started.
|
||||||
|
|
||||||
|
## Isolated JavaFX runtime inspection
|
||||||
|
|
||||||
|
Constructed the actual EMC with empty status-service views, the real Perps sample service behind an instrumented read-only view, and the real JavaFX runtime. All instrumented snapshot calls ran outside the JavaFX thread.
|
||||||
|
|
||||||
|
- Initial retrieval populated both environments with two wallets, four venues and eight positions: 14 expanded rows.
|
||||||
|
- Measured the first two scheduled gaps as 2.999846105 and 2.999953679 seconds.
|
||||||
|
- Collapsed wallet A and selected a position in wallet B. Repeated polls preserved the same selection and expansion. List/property listeners observed zero tree mutations for unchanged data. Test remained independently expanded.
|
||||||
|
- Inspected rendered images for the original column headings, sample/Phoenix notice, USD formatting, faint Long/Short colors and selection styling.
|
||||||
|
- Injected one read failure; the following scheduled read recovered and polling continued.
|
||||||
|
- Blocked one read and successfully dispatched UI work while it waited; hiding the window interrupted the blocked read and stopped subsequent polling.
|
||||||
|
- Reopened EMC and confirmed retained selection. Held an old session's queued completion across hide, emptied the preview's view, then delivered that obsolete completion; it did not repopulate the view.
|
||||||
|
- Reopened again and issued repeated start requests while visible. Exactly two additional reads occurred over 6.4 seconds, demonstrating one three-second loop rather than accumulated loops.
|
||||||
|
|
||||||
|
Images are temporary local evidence in `/tmp/emc96-preview/`: `expanded.png`, `collapsed-selected.png`, `after-polling.png` and `reopened.png`. Inspection exercised actual JavaFX scenes and controls; manual mouse/keyboard use in the operational deployment was not exercised.
|
||||||
|
|
||||||
|
## Review state
|
||||||
|
|
||||||
|
All work remains local, uncommitted and unpushed. The OpenSpec change remains active for owner review; its delta specs have not been archived into the main specs. The new service will autostart on the next normal Hub startup using this deployment's updated local configuration.
|
||||||
@@ -28,10 +28,29 @@ The overview SHALL include hardcoded Jupiter and Phoenix venues with multiple sa
|
|||||||
- **AND** only position rows use direction-based backgrounds, with direction also conveyed by text
|
- **AND** only position rows use direction-based backgrounds, with direction also conveyed by text
|
||||||
|
|
||||||
### Requirement: Prototype is clearly labeled and entirely local
|
### Requirement: Prototype is clearly labeled and entirely local
|
||||||
The Perps view SHALL explicitly label its data as sample data and explain that Phoenix rows are illustrative without an existing integration. It SHALL remain entirely within EMC, using no network calls, service changes, service communication, Refresh button, or simulated live updates for the prototype.
|
The Perps view SHALL explicitly label its data as sample data and explain that Phoenix rows are illustrative without an existing integration. It SHALL obtain positions only from the Perps service's current in-memory snapshot, with no external network calls, Refresh button, or simulated position changes. EMC SHALL own all UI components, row colors and formatting.
|
||||||
|
|
||||||
#### Scenario: Inspect the prototype boundary
|
#### Scenario: Inspect the prototype boundary
|
||||||
- **WHEN** the Perps tab is displayed
|
- **WHEN** the Perps tab is displayed
|
||||||
- **THEN** sample-data and illustrative-Phoenix labels are visible
|
- **THEN** sample-data and illustrative-Phoenix labels are visible
|
||||||
- **AND** there is no Refresh button or automated update of sample positions
|
- **AND** there is no Refresh button or simulated update of sample positions
|
||||||
- **AND** displaying and interacting with the prototype uses only local EMC data
|
- **AND** displaying and interacting with the prototype uses only the service's local sample snapshot
|
||||||
|
|
||||||
|
### Requirement: Asynchronous polling follows window visibility
|
||||||
|
EMC SHALL request the current Perps snapshot immediately on opening and every three seconds while visible, without blocking the UI thread. UI updates SHALL occur only on the JavaFX thread. Hiding the window SHALL stop polling; reopening SHALL create only one active polling loop and ignore results belonging to an earlier window session. Poll failures SHALL preserve the current view and allow subsequent attempts.
|
||||||
|
|
||||||
|
#### Scenario: Read takes time or fails
|
||||||
|
- **WHEN** a snapshot read is delayed or fails
|
||||||
|
- **THEN** the UI remains responsive and the existing view is retained
|
||||||
|
- **AND** later polling remains possible without overlapping scheduled reads
|
||||||
|
|
||||||
|
#### Scenario: Hide and reopen
|
||||||
|
- **WHEN** the owner hides and reopens EMC repeatedly
|
||||||
|
- **THEN** each visible session has only one polling loop and obsolete results cannot update its views
|
||||||
|
|
||||||
|
### Requirement: Polling preserves Perps view state
|
||||||
|
The Perps table SHALL retain matching wallet, venue and position identities across snapshots. Repeated unchanged snapshots SHALL leave existing rows, expansion, selection and formatting untouched without flicker. New groups SHALL be expanded by default, and future added or removed positions SHALL be reconciled by identity without resetting retained groups.
|
||||||
|
|
||||||
|
#### Scenario: Poll unchanged positions
|
||||||
|
- **WHEN** a user collapses a group or selects a position and repeated snapshots are unchanged
|
||||||
|
- **THEN** the same rows remain, with the same expansion and selection and no rebuild or refresh
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# 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, and SHALL make no Jupiter or Phoenix requests.
|
||||||
|
|
||||||
|
#### Scenario: Repeated reads
|
||||||
|
- **WHEN** clients repeatedly retrieve the started service's snapshot
|
||||||
|
- **THEN** they receive the same complete positions with stable identifiers and cannot mutate service-owned collections
|
||||||
|
- **AND** no exchange requests or position-management operations occur
|
||||||
|
|
||||||
|
### Requirement: Service owns the unchanged sample positions
|
||||||
|
The service SHALL own the existing eight sample positions grouped under Sample wallet A and Sample wallet B and their Jupiter and Phoenix venues, retaining every existing instrument, direction and decimal USD size. The snapshot SHALL remain unchanged for this task.
|
||||||
|
|
||||||
|
#### Scenario: Existing samples are retrieved
|
||||||
|
- **WHEN** EMC retrieves the service snapshot
|
||||||
|
- **THEN** wallet A contains Jupiter SOL Long 12500.00 and BTC Short 8250.50, Phoenix SOL Short 3200.75 and ETH Long 5400.00
|
||||||
|
- **AND** wallet B contains Jupiter ETH Long 1875.25 and SOL Short 950.00, Phoenix BTC Long 21000.00 and SOL Short 640.50
|
||||||
|
|
||||||
|
### Requirement: Hub owns Perps startup
|
||||||
|
The service SHALL run as a separate Hub-managed process registered as `evelyn.perps.default`. The service API SHALL expose no lifecycle operations. Snapshot reads before process startup SHALL fail clearly.
|
||||||
|
|
||||||
|
#### Scenario: Service starts
|
||||||
|
- **WHEN** the Hub starts the registered Perps process
|
||||||
|
- **THEN** its service view makes the complete sample snapshot available without starting a polling or integration worker
|
||||||
@@ -89,9 +89,9 @@ After validating the entire plan, the Hub SHALL construct the configured Registr
|
|||||||
- **THEN** its deployment configuration, dedicated signer/wallet, Jupiter service and direct Raydium service are neither loaded nor constructed nor registered
|
- **THEN** its deployment configuration, dedicated signer/wallet, Jupiter service and direct Raydium service are neither loaded nor constructed nor registered
|
||||||
|
|
||||||
### Requirement: Selected processes are constructed and started lazily in exact order
|
### Requirement: Selected processes are constructed and started lazily in exact order
|
||||||
The Hub SHALL support the modern process IDs `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `jupiter-perps-alarm.default`, `evelyn.status.prod`, `evelyn.status.test`, `evelyn.iou-burner.prod`, `evelyn.liquidity.prod`, `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default` through implementation-only construction capability that creates no process instance during validation. After passive composition, the Hub SHALL visit every configured entry after Registry in declared order, beginning with Journal. At each entry it SHALL construct only that process and any explicitly composed process-specific passive dependencies or administration view, register the process under the exact configured ID, resolve it through the canonical `NenjimProcess` contract where applicable, call `start()`, and record the ID only after successful return.
|
The Hub SHALL support the modern process IDs `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `jupiter-perps-alarm.default`, `evelyn.status.prod`, `evelyn.status.test`, `evelyn.iou-burner.prod`, `evelyn.liquidity.prod`, `evelyn.perps.default`, `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default` through implementation-only construction capability that creates no process instance during validation. After passive composition, the Hub SHALL visit every configured entry after Registry in declared order, beginning with Journal. At each entry it SHALL construct only that process and any explicitly composed process-specific passive dependencies or administration view, register the process under the exact configured ID, resolve it through the canonical `NenjimProcess` contract where applicable, call `start()`, and record the ID only after successful return.
|
||||||
|
|
||||||
The production IOU burner SHALL receive the already constructed Production Evelyn status service, and SHALL require both Ticker and `evelyn.status.prod` to precede `evelyn.iou-burner.prod`. The configuration SHALL be the complete manually ordered process dependency closure. The Hub SHALL not discover, insert, recursively construct, reorder, or automatically start dependencies. A process omitted from `[AUTO_START]` SHALL not be constructed, registered, started, or recorded, even when its implementation is available. Legacy process prototypes outside the modern Hub graph SHALL not be selectable.
|
The production IOU burner SHALL receive the already constructed Production Evelyn status service, and SHALL require both Ticker and `evelyn.status.prod` to precede `evelyn.iou-burner.prod`. EMC SHALL receive the already started `evelyn.perps.default` service, which SHALL precede EMC in the deployment autostart list. The configuration SHALL be the complete manually ordered process dependency closure. The Hub SHALL not discover, insert, recursively construct, reorder, or automatically start dependencies. A process omitted from `[AUTO_START]` SHALL not be constructed, registered, started, or recorded, even when its implementation is available. Legacy process prototypes outside the modern Hub graph SHALL not be selectable.
|
||||||
|
|
||||||
#### Scenario: Configured process starts successfully
|
#### Scenario: Configured process starts successfully
|
||||||
- **WHEN** a configured process entry is reached and its dependencies are already available
|
- **WHEN** a configured process entry is reached and its dependencies are already available
|
||||||
@@ -133,6 +133,14 @@ The production IOU burner SHALL receive the already constructed Production Evely
|
|||||||
- **WHEN** the burner is reached without a previously constructed Production status service
|
- **WHEN** the burner is reached without a previously constructed Production status service
|
||||||
- **THEN** construction fails clearly without creating or starting the omitted dependency or the burner
|
- **THEN** construction fails clearly without creating or starting the omitted dependency or the burner
|
||||||
|
|
||||||
|
#### Scenario: EMC has its Perps dependency
|
||||||
|
- **WHEN** `evelyn.perps.default` appears before EMC in the deployment's ordered autostart list
|
||||||
|
- **THEN** the Hub registers and starts that service and injects its service view into EMC
|
||||||
|
|
||||||
|
#### Scenario: EMC precedes Perps
|
||||||
|
- **WHEN** EMC is selected without an earlier Perps process
|
||||||
|
- **THEN** construction fails clearly without inserting or starting that dependency
|
||||||
|
|
||||||
### Requirement: Configured startup is fail-fast and reports actual milestones
|
### Requirement: Configured startup is fail-fast and reports actual milestones
|
||||||
Registry and every selected process SHALL be registered before its `start()` call and appended to the Hub's immutable ordered startup snapshot only after `start()` returns successfully. Construction failure SHALL leave the selected process unregistered. Registration or startup failure SHALL stop the plan before any later entry is constructed or started; a startup failure MAY leave that process registered but SHALL not record it. Earlier successful processes SHALL remain in snapshots, no rollback or retry SHALL occur, and the Hub SHALL announce its existing online point and enter its blocking runtime wait only after every configured process succeeds.
|
Registry and every selected process SHALL be registered before its `start()` call and appended to the Hub's immutable ordered startup snapshot only after `start()` returns successfully. Construction failure SHALL leave the selected process unregistered. Registration or startup failure SHALL stop the plan before any later entry is constructed or started; a startup failure MAY leave that process registered but SHALL not record it. Earlier successful processes SHALL remain in snapshots, no rollback or retry SHALL occur, and the Hub SHALL announce its existing online point and enter its blocking runtime wait only after every configured process succeeds.
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package com.r35157.evelyn.emc.impl.ref;
|
package com.r35157.evelyn.emc.impl.ref;
|
||||||
|
|
||||||
import com.r35157.evelyn.service.status.EvelynStatusService;
|
import com.r35157.evelyn.service.status.EvelynStatusService;
|
||||||
|
import com.r35157.evelyn.service.perps.EvelynPerpsService;
|
||||||
|
import com.r35157.evelyn.service.perps.PerpsSnapshot;
|
||||||
import com.fanitas.evelyn.core.EvelynStatusIndexPoint;
|
import com.fanitas.evelyn.core.EvelynStatusIndexPoint;
|
||||||
import com.r35157.evelyn.emc.EvelynMissionControl;
|
import com.r35157.evelyn.emc.EvelynMissionControl;
|
||||||
import com.r35157.nenjim.process.NenjimProcess;
|
import com.r35157.nenjim.process.NenjimProcess;
|
||||||
import com.r35157.service.javafx.JavaFxRuntimeService;
|
import com.r35157.service.javafx.JavaFxRuntimeService;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import javafx.animation.KeyFrame;
|
import javafx.animation.KeyFrame;
|
||||||
import javafx.animation.Timeline;
|
import javafx.animation.Timeline;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
@@ -26,6 +30,9 @@ import java.time.ZoneId;
|
|||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public final class EvelynMissionControlImpl implements EvelynMissionControl, NenjimProcess {
|
public final class EvelynMissionControlImpl implements EvelynMissionControl, NenjimProcess {
|
||||||
@@ -34,10 +41,12 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl, Nen
|
|||||||
public EvelynMissionControlImpl(
|
public EvelynMissionControlImpl(
|
||||||
@NotNull EvelynStatusService evelynStatusServiceProd,
|
@NotNull EvelynStatusService evelynStatusServiceProd,
|
||||||
@NotNull EvelynStatusService evelynStatusServiceTest,
|
@NotNull EvelynStatusService evelynStatusServiceTest,
|
||||||
|
@NotNull EvelynPerpsService evelynPerpsService,
|
||||||
@NotNull JavaFxRuntimeService javaFxRuntimeService
|
@NotNull JavaFxRuntimeService javaFxRuntimeService
|
||||||
) {
|
) {
|
||||||
this.evelynStatusProd = Objects.requireNonNull(evelynStatusServiceProd, "evelynStatusServiceProd");
|
this.evelynStatusProd = Objects.requireNonNull(evelynStatusServiceProd, "evelynStatusServiceProd");
|
||||||
this.evelynStatusTest = Objects.requireNonNull(evelynStatusServiceTest, "evelynStatusServiceTest");
|
this.evelynStatusTest = Objects.requireNonNull(evelynStatusServiceTest, "evelynStatusServiceTest");
|
||||||
|
this.evelynPerpsService = Objects.requireNonNull(evelynPerpsService, "evelynPerpsService");
|
||||||
this.javaFxRuntimeService = Objects.requireNonNull(
|
this.javaFxRuntimeService = Objects.requireNonNull(
|
||||||
javaFxRuntimeService,
|
javaFxRuntimeService,
|
||||||
"javaFxRuntimeService");
|
"javaFxRuntimeService");
|
||||||
@@ -145,23 +154,28 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl, Nen
|
|||||||
if (window == null) {
|
if (window == null) {
|
||||||
window = new Stage();
|
window = new Stage();
|
||||||
window.setTitle("Evelyn Mission Control");
|
window.setTitle("Evelyn Mission Control");
|
||||||
window.setOnHidden(event -> refreshTimeline.stop());
|
window.setOnHidden(event -> {
|
||||||
}
|
refreshTimeline.stop();
|
||||||
|
stopPerpsPolling();
|
||||||
|
});
|
||||||
window.setScene(new Scene(createRootTabs(), 600, 400));
|
window.setScene(new Scene(createRootTabs(), 600, 400));
|
||||||
|
}
|
||||||
|
|
||||||
refreshCharts();
|
refreshCharts();
|
||||||
window.show();
|
window.show();
|
||||||
refreshTimeline.playFromStart();
|
refreshTimeline.playFromStart();
|
||||||
|
startPerpsPolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
private @NotNull TabPane createMissionControlTabs(
|
private @NotNull TabPane createMissionControlTabs(
|
||||||
@NotNull String color,
|
@NotNull String color,
|
||||||
@NotNull StatusIndexChart statusIndexChart
|
@NotNull StatusIndexChart statusIndexChart,
|
||||||
|
@NotNull PerpsTab perpsTab
|
||||||
) {
|
) {
|
||||||
TabPane tabs = new TabPane(
|
TabPane tabs = new TabPane(
|
||||||
createOverviewTab(color, statusIndexChart),
|
createOverviewTab(color, statusIndexChart),
|
||||||
createMissionControlTab("Portfolio", color),
|
createMissionControlTab("Portfolio", color),
|
||||||
PerpsTab.create(color),
|
perpsTab.tab(),
|
||||||
createMissionControlTab("Spot", color),
|
createMissionControlTab("Spot", color),
|
||||||
createMissionControlTab("Liquidity Pools", color),
|
createMissionControlTab("Liquidity Pools", color),
|
||||||
createMissionControlTab("Alarms", color),
|
createMissionControlTab("Alarms", color),
|
||||||
@@ -239,16 +253,18 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl, Nen
|
|||||||
private @NotNull TabPane createRootTabs() {
|
private @NotNull TabPane createRootTabs() {
|
||||||
productionChart = createStatusIndexChart();
|
productionChart = createStatusIndexChart();
|
||||||
testChart = createStatusIndexChart();
|
testChart = createStatusIndexChart();
|
||||||
|
productionPerps = new PerpsTab(PRODUCTION_COLOR);
|
||||||
|
testPerps = new PerpsTab(TEST_COLOR);
|
||||||
|
|
||||||
Tab productionTab = new Tab(
|
Tab productionTab = new Tab(
|
||||||
"Production",
|
"Production",
|
||||||
createMissionControlTabs(PRODUCTION_COLOR, productionChart)
|
createMissionControlTabs(PRODUCTION_COLOR, productionChart, productionPerps)
|
||||||
);
|
);
|
||||||
productionTab.setStyle("-fx-background-color: " + PRODUCTION_COLOR + ";");
|
productionTab.setStyle("-fx-background-color: " + PRODUCTION_COLOR + ";");
|
||||||
|
|
||||||
Tab testTab = new Tab(
|
Tab testTab = new Tab(
|
||||||
"Test",
|
"Test",
|
||||||
createMissionControlTabs(TEST_COLOR, testChart)
|
createMissionControlTabs(TEST_COLOR, testChart, testPerps)
|
||||||
);
|
);
|
||||||
testTab.setStyle("-fx-background-color: " + TEST_COLOR + ";");
|
testTab.setStyle("-fx-background-color: " + TEST_COLOR + ";");
|
||||||
|
|
||||||
@@ -262,6 +278,37 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl, Nen
|
|||||||
refreshChart(productionChart, evelynStatusProd.getStatusIndexHistory());
|
refreshChart(productionChart, evelynStatusProd.getStatusIndexHistory());
|
||||||
refreshChart(testChart, evelynStatusTest.getStatusIndexHistory());
|
refreshChart(testChart, evelynStatusTest.getStatusIndexHistory());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void startPerpsPolling() {
|
||||||
|
stopPerpsPolling();
|
||||||
|
ScheduledExecutorService session = Executors.newSingleThreadScheduledExecutor(runnable -> {
|
||||||
|
Thread thread = new Thread(runnable, "emc-perps-poll");
|
||||||
|
thread.setDaemon(true);
|
||||||
|
return thread;
|
||||||
|
});
|
||||||
|
perpsPolling = session;
|
||||||
|
session.scheduleAtFixedRate(() -> {
|
||||||
|
try {
|
||||||
|
PerpsSnapshot snapshot = evelynPerpsService.getPositionsSnapshot();
|
||||||
|
javaFxRuntimeService.runLater(() -> {
|
||||||
|
if (perpsPolling == session && window.isShowing()) {
|
||||||
|
productionPerps.update(snapshot);
|
||||||
|
testPerps.update(snapshot);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
log.warn("Unable to refresh Perps positions", exception);
|
||||||
|
}
|
||||||
|
}, 0, PERPS_REFRESH_SECONDS, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopPerpsPolling() {
|
||||||
|
ScheduledExecutorService session = perpsPolling;
|
||||||
|
perpsPolling = null;
|
||||||
|
if (session != null) {
|
||||||
|
session.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Inner classes
|
// region Inner classes
|
||||||
@@ -307,6 +354,8 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl, Nen
|
|||||||
private static final String TEST_COLOR = "#d8f3dc";
|
private static final String TEST_COLOR = "#d8f3dc";
|
||||||
private static final double EMPTY_Y_BOUND = 1.0;
|
private static final double EMPTY_Y_BOUND = 1.0;
|
||||||
private static final double CHART_REFRESH_SECONDS = 1.0;
|
private static final double CHART_REFRESH_SECONDS = 1.0;
|
||||||
|
private static final long PERPS_REFRESH_SECONDS = 3;
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(EvelynMissionControlImpl.class);
|
||||||
private static final long SINGLE_POINT_X_PADDING_MILLIS = 30_000;
|
private static final long SINGLE_POINT_X_PADDING_MILLIS = 30_000;
|
||||||
private static final DateTimeFormatter TIMESTAMP_TICK_FORMATTER =
|
private static final DateTimeFormatter TIMESTAMP_TICK_FORMATTER =
|
||||||
DateTimeFormatter.ofPattern("HH:mm dd/MM/yy")
|
DateTimeFormatter.ofPattern("HH:mm dd/MM/yy")
|
||||||
@@ -333,6 +382,7 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl, Nen
|
|||||||
// region Dependencies
|
// region Dependencies
|
||||||
private final EvelynStatusService evelynStatusProd;
|
private final EvelynStatusService evelynStatusProd;
|
||||||
private final EvelynStatusService evelynStatusTest;
|
private final EvelynStatusService evelynStatusTest;
|
||||||
|
private final EvelynPerpsService evelynPerpsService;
|
||||||
private final JavaFxRuntimeService javaFxRuntimeService;
|
private final JavaFxRuntimeService javaFxRuntimeService;
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
@@ -340,5 +390,8 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl, Nen
|
|||||||
private Stage window;
|
private Stage window;
|
||||||
private StatusIndexChart productionChart;
|
private StatusIndexChart productionChart;
|
||||||
private StatusIndexChart testChart;
|
private StatusIndexChart testChart;
|
||||||
|
private PerpsTab productionPerps;
|
||||||
|
private PerpsTab testPerps;
|
||||||
|
private ScheduledExecutorService perpsPolling;
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.r35157.evelyn.emc.impl.ref;
|
package com.r35157.evelyn.emc.impl.ref;
|
||||||
|
|
||||||
|
import com.r35157.evelyn.service.perps.PerpsSnapshot;
|
||||||
|
import com.r35157.evelyn.service.perps.PerpsSnapshot.Direction;
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
@@ -13,10 +16,15 @@ import javafx.scene.layout.VBox;
|
|||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
final class PerpsTab {
|
final class PerpsTab {
|
||||||
static Tab create(String environmentColor) {
|
PerpsTab(String environmentColor) {
|
||||||
NumberFormat usdFormat = NumberFormat.getCurrencyInstance(Locale.US);
|
NumberFormat usdFormat = NumberFormat.getCurrencyInstance(Locale.US);
|
||||||
|
|
||||||
TreeTableColumn<PositionRow, String> position = new TreeTableColumn<>("Position");
|
TreeTableColumn<PositionRow, String> position = new TreeTableColumn<>("Position");
|
||||||
@@ -28,7 +36,7 @@ final class PerpsTab {
|
|||||||
TreeTableColumn<PositionRow, String> direction = new TreeTableColumn<>("Direction");
|
TreeTableColumn<PositionRow, String> direction = new TreeTableColumn<>("Direction");
|
||||||
direction.setCellValueFactory(cell -> {
|
direction.setCellValueFactory(cell -> {
|
||||||
Direction value = cell.getValue().getValue().direction();
|
Direction value = cell.getValue().getValue().direction();
|
||||||
return new ReadOnlyStringWrapper(value == null ? "" : value.label);
|
return new ReadOnlyStringWrapper(value == null ? "" : value == Direction.LONG ? "Long" : "Short");
|
||||||
});
|
});
|
||||||
direction.setPrefWidth(90);
|
direction.setPrefWidth(90);
|
||||||
direction.setMinWidth(80);
|
direction.setMinWidth(80);
|
||||||
@@ -44,7 +52,7 @@ final class PerpsTab {
|
|||||||
size.setStyle("-fx-alignment: CENTER-RIGHT;");
|
size.setStyle("-fx-alignment: CENTER-RIGHT;");
|
||||||
size.setSortable(false);
|
size.setSortable(false);
|
||||||
|
|
||||||
TreeTableView<PositionRow> table = new TreeTableView<>(createSampleRoot());
|
table.getRoot().setExpanded(true);
|
||||||
table.setShowRoot(false);
|
table.setShowRoot(false);
|
||||||
table.setTreeColumn(position);
|
table.setTreeColumn(position);
|
||||||
table.getColumns().add(position);
|
table.getColumns().add(position);
|
||||||
@@ -57,7 +65,7 @@ final class PerpsTab {
|
|||||||
super.updateItem(item, empty);
|
super.updateItem(item, empty);
|
||||||
setStyle(empty || item == null || item.direction() == null
|
setStyle(empty || item == null || item.direction() == null
|
||||||
? ""
|
? ""
|
||||||
: item.direction().rowStyle);
|
: item.direction() == Direction.LONG ? LONG_ROW_STYLE : SHORT_ROW_STYLE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -72,58 +80,93 @@ final class PerpsTab {
|
|||||||
content.setStyle("-fx-background-color: " + environmentColor + ";");
|
content.setStyle("-fx-background-color: " + environmentColor + ";");
|
||||||
VBox.setVgrow(table, Priority.ALWAYS);
|
VBox.setVgrow(table, Priority.ALWAYS);
|
||||||
|
|
||||||
Tab tab = new Tab("Perps", content);
|
tab = new Tab("Perps", content);
|
||||||
tab.setStyle("-fx-background-color: " + environmentColor + ";");
|
tab.setStyle("-fx-background-color: " + environmentColor + ";");
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab tab() {
|
||||||
return tab;
|
return tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TreeItem<PositionRow> createSampleRoot() {
|
void update(PerpsSnapshot snapshot) {
|
||||||
return group("Sample positions",
|
if (!Platform.isFxApplicationThread()) {
|
||||||
group("Sample wallet A",
|
throw new IllegalStateException("Perps views must be updated on the JavaFX Application Thread");
|
||||||
group("Jupiter",
|
}
|
||||||
position("SOL-PERP", Direction.LONG, "12500.00"),
|
Objects.requireNonNull(snapshot, "snapshot");
|
||||||
position("BTC-PERP", Direction.SHORT, "8250.50")),
|
if (snapshot.equals(renderedSnapshot)) {
|
||||||
group("Phoenix",
|
return;
|
||||||
position("SOL-PERP", Direction.SHORT, "3200.75"),
|
|
||||||
position("ETH-PERP", Direction.LONG, "5400.00"))),
|
|
||||||
group("Sample wallet B",
|
|
||||||
group("Jupiter",
|
|
||||||
position("ETH-PERP", Direction.LONG, "1875.25"),
|
|
||||||
position("SOL-PERP", Direction.SHORT, "950.00")),
|
|
||||||
group("Phoenix",
|
|
||||||
position("BTC-PERP", Direction.LONG, "21000.00"),
|
|
||||||
position("SOL-PERP", Direction.SHORT, "640.50"))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SafeVarargs
|
TreeItem<PositionRow> selected = table.getSelectionModel().getSelectedItem();
|
||||||
private static TreeItem<PositionRow> group(String label, TreeItem<PositionRow>... children) {
|
List<TreeItem<PositionRow>> wallets = new ArrayList<>();
|
||||||
TreeItem<PositionRow> item = new TreeItem<>(new PositionRow(label, null, null));
|
Map<String, TreeItem<PositionRow>> existingWallets = childrenById(table.getRoot());
|
||||||
item.getChildren().addAll(children);
|
for (PerpsSnapshot.Wallet wallet : snapshot.wallets()) {
|
||||||
|
TreeItem<PositionRow> walletItem = item(existingWallets, group(wallet.id(), wallet.name()));
|
||||||
|
List<TreeItem<PositionRow>> venues = new ArrayList<>();
|
||||||
|
Map<String, TreeItem<PositionRow>> existingVenues = childrenById(walletItem);
|
||||||
|
for (PerpsSnapshot.Venue venue : wallet.venues()) {
|
||||||
|
TreeItem<PositionRow> venueItem = item(existingVenues, group(venue.id(), venue.name()));
|
||||||
|
List<TreeItem<PositionRow>> positions = new ArrayList<>();
|
||||||
|
Map<String, TreeItem<PositionRow>> existingPositions = childrenById(venueItem);
|
||||||
|
for (PerpsSnapshot.Position position : venue.positions()) {
|
||||||
|
positions.add(item(existingPositions, new PositionRow(
|
||||||
|
position.id(), position.instrument(), position.direction(), position.sizeUsd())));
|
||||||
|
}
|
||||||
|
updateChildren(venueItem, positions);
|
||||||
|
venues.add(venueItem);
|
||||||
|
}
|
||||||
|
updateChildren(walletItem, venues);
|
||||||
|
wallets.add(walletItem);
|
||||||
|
}
|
||||||
|
updateChildren(table.getRoot(), wallets);
|
||||||
|
if (selected != null && table.getSelectionModel().getSelectedItem() != selected) {
|
||||||
|
int selectedRow = table.getRow(selected);
|
||||||
|
if (selectedRow >= 0) {
|
||||||
|
table.getSelectionModel().select(selectedRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
renderedSnapshot = snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PositionRow group(String id, String label) {
|
||||||
|
return new PositionRow(id, label, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, TreeItem<PositionRow>> childrenById(TreeItem<PositionRow> parent) {
|
||||||
|
Map<String, TreeItem<PositionRow>> children = new HashMap<>();
|
||||||
|
for (TreeItem<PositionRow> child : parent.getChildren()) {
|
||||||
|
children.put(child.getValue().id(), child);
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TreeItem<PositionRow> item(
|
||||||
|
Map<String, TreeItem<PositionRow>> existing, PositionRow row) {
|
||||||
|
TreeItem<PositionRow> item = existing.get(row.id());
|
||||||
|
if (item == null) {
|
||||||
|
item = new TreeItem<>(row);
|
||||||
item.setExpanded(true);
|
item.setExpanded(true);
|
||||||
|
} else if (!row.equals(item.getValue())) {
|
||||||
|
item.setValue(row);
|
||||||
|
}
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TreeItem<PositionRow> position(String label, Direction direction, String sizeUsd) {
|
private static void updateChildren(TreeItem<PositionRow> parent, List<TreeItem<PositionRow>> children) {
|
||||||
return new TreeItem<>(new PositionRow(label, direction, new BigDecimal(sizeUsd)));
|
if (!parent.getChildren().equals(children)) {
|
||||||
|
parent.getChildren().setAll(children);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private record PositionRow(String label, Direction direction, ΩAmountΩ sizeUsd) {
|
private record PositionRow(String id, String label, Direction direction, ΩAmountΩ sizeUsd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum Direction {
|
private static final String LONG_ROW_STYLE = "-fx-control-inner-background: #e6f4ea;"
|
||||||
LONG("Long", "#e6f4ea"),
|
+ "-fx-control-inner-background-alt: #e6f4ea;";
|
||||||
SHORT("Short", "#fce8e6");
|
private static final String SHORT_ROW_STYLE = "-fx-control-inner-background: #fce8e6;"
|
||||||
|
+ "-fx-control-inner-background-alt: #fce8e6;";
|
||||||
Direction(String label, String background) {
|
private final Tab tab;
|
||||||
this.label = label;
|
private final TreeTableView<PositionRow> table = new TreeTableView<>(
|
||||||
this.rowStyle = "-fx-control-inner-background: " + background + ";"
|
new TreeItem<>(group("root", "Sample positions")));
|
||||||
+ "-fx-control-inner-background-alt: " + background + ";";
|
private PerpsSnapshot renderedSnapshot;
|
||||||
}
|
|
||||||
|
|
||||||
private final String label;
|
|
||||||
private final String rowStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PerpsTab() {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.r35157.evelyn.service.perps;
|
||||||
|
|
||||||
|
import com.r35157.nenjim.component.NenjimComponent;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/** Read-only current positions; startup belongs to the Hub, refresh timing to the caller. */
|
||||||
|
public interface EvelynPerpsService extends NenjimComponent {
|
||||||
|
/** Returns the complete immutable in-memory snapshot without making exchange requests. */
|
||||||
|
@NotNull PerpsSnapshot getPositionsSnapshot();
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.r35157.evelyn.service.perps;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/** IDs are stable across reads and unique among siblings, independently of display names. */
|
||||||
|
public record PerpsSnapshot(List<Wallet> wallets) {
|
||||||
|
public PerpsSnapshot {
|
||||||
|
wallets = List.copyOf(wallets);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Wallet(String id, String name, List<Venue> venues) {
|
||||||
|
public Wallet {
|
||||||
|
Objects.requireNonNull(id, "id");
|
||||||
|
Objects.requireNonNull(name, "name");
|
||||||
|
venues = List.copyOf(venues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Venue(String id, String name, List<Position> positions) {
|
||||||
|
public Venue {
|
||||||
|
Objects.requireNonNull(id, "id");
|
||||||
|
Objects.requireNonNull(name, "name");
|
||||||
|
positions = List.copyOf(positions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Position(String id, String instrument, Direction direction, ΩAmountΩ sizeUsd) {
|
||||||
|
public Position {
|
||||||
|
Objects.requireNonNull(id, "id");
|
||||||
|
Objects.requireNonNull(instrument, "instrument");
|
||||||
|
Objects.requireNonNull(direction, "direction");
|
||||||
|
Objects.requireNonNull(sizeUsd, "sizeUsd");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Direction {
|
||||||
|
LONG, SHORT
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.r35157.evelyn.service.perps.impl.ref;
|
||||||
|
|
||||||
|
import com.r35157.evelyn.service.perps.EvelynPerpsService;
|
||||||
|
import com.r35157.evelyn.service.perps.PerpsSnapshot;
|
||||||
|
import com.r35157.evelyn.service.perps.PerpsSnapshot.Position;
|
||||||
|
import com.r35157.evelyn.service.perps.PerpsSnapshot.Venue;
|
||||||
|
import com.r35157.evelyn.service.perps.PerpsSnapshot.Wallet;
|
||||||
|
import com.r35157.evelyn.service.perps.PerpsSnapshot.Direction;
|
||||||
|
import com.r35157.nenjim.process.NenjimProcess;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Owns the fixed examples only. No exchange clients, position management or update worker. */
|
||||||
|
public final class EvelynPerpsServiceImpl implements EvelynPerpsService, NenjimProcess {
|
||||||
|
@Override
|
||||||
|
public synchronized void start() {
|
||||||
|
if (snapshot != null) {
|
||||||
|
throw new IllegalStateException("Perps service has already started");
|
||||||
|
}
|
||||||
|
snapshot = new PerpsSnapshot(List.of(
|
||||||
|
new Wallet("sample-wallet-a", "Sample wallet A", List.of(
|
||||||
|
new Venue("jupiter", "Jupiter", List.of(
|
||||||
|
position("sol-long", "SOL-PERP", Direction.LONG, "12500.00"),
|
||||||
|
position("btc-short", "BTC-PERP", Direction.SHORT, "8250.50"))),
|
||||||
|
new Venue("phoenix", "Phoenix", List.of(
|
||||||
|
position("sol-short", "SOL-PERP", Direction.SHORT, "3200.75"),
|
||||||
|
position("eth-long", "ETH-PERP", Direction.LONG, "5400.00"))))),
|
||||||
|
new Wallet("sample-wallet-b", "Sample wallet B", List.of(
|
||||||
|
new Venue("jupiter", "Jupiter", List.of(
|
||||||
|
position("eth-long", "ETH-PERP", Direction.LONG, "1875.25"),
|
||||||
|
position("sol-short", "SOL-PERP", Direction.SHORT, "950.00"))),
|
||||||
|
new Venue("phoenix", "Phoenix", List.of(
|
||||||
|
position("btc-long", "BTC-PERP", Direction.LONG, "21000.00"),
|
||||||
|
position("sol-short", "SOL-PERP", Direction.SHORT, "640.50")))))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull PerpsSnapshot getPositionsSnapshot() {
|
||||||
|
PerpsSnapshot current = snapshot;
|
||||||
|
if (current == null) {
|
||||||
|
throw new IllegalStateException("Perps service has not started");
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Position position(String id, String instrument, Direction direction, String sizeUsd) {
|
||||||
|
return new Position(id, instrument, direction, new ΩAmountΩ(sizeUsd));
|
||||||
|
}
|
||||||
|
|
||||||
|
private volatile PerpsSnapshot snapshot;
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ import com.r35157.evelyn.service.liquidity.impl.ref.EvelynLiquidityConfiguration
|
|||||||
import com.r35157.evelyn.service.liquidity.impl.ref.EvelynLiquidityServiceImpl;
|
import com.r35157.evelyn.service.liquidity.impl.ref.EvelynLiquidityServiceImpl;
|
||||||
import com.r35157.evelyn.service.status.EvelynStatusService;
|
import com.r35157.evelyn.service.status.EvelynStatusService;
|
||||||
import com.r35157.evelyn.service.status.impl.ref.EvelynStatusServiceImpl;
|
import com.r35157.evelyn.service.status.impl.ref.EvelynStatusServiceImpl;
|
||||||
|
import com.r35157.evelyn.service.perps.EvelynPerpsService;
|
||||||
|
import com.r35157.evelyn.service.perps.impl.ref.EvelynPerpsServiceImpl;
|
||||||
import com.fanitas.evelyn.service.burner.EvelynIOUBurnerService;
|
import com.fanitas.evelyn.service.burner.EvelynIOUBurnerService;
|
||||||
import com.fanitas.evelyn.service.burner.impl.ref.EvelynIOUBurnerServiceImpl;
|
import com.fanitas.evelyn.service.burner.impl.ref.EvelynIOUBurnerServiceImpl;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
@@ -358,6 +360,12 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
solanaBlockChain, cis, liquidityConfiguration);
|
solanaBlockChain, cis, liquidityConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private NenjimProcess constructEvelynPerpsProcess() {
|
||||||
|
EvelynPerpsServiceImpl process = new EvelynPerpsServiceImpl();
|
||||||
|
evelynPerpsService = process;
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
private NenjimProcess constructJavaFxRuntimeProcess() {
|
private NenjimProcess constructJavaFxRuntimeProcess() {
|
||||||
JavaFxRuntimeServiceImpl process = new JavaFxRuntimeServiceImpl();
|
JavaFxRuntimeServiceImpl process = new JavaFxRuntimeServiceImpl();
|
||||||
javaFxRuntimeService = process;
|
javaFxRuntimeService = process;
|
||||||
@@ -374,6 +382,10 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
evelynStatusServiceTest,
|
evelynStatusServiceTest,
|
||||||
EVELYN_MISSION_CONTROL_ID,
|
EVELYN_MISSION_CONTROL_ID,
|
||||||
EVELYN_STATUS_TEST_ID),
|
EVELYN_STATUS_TEST_ID),
|
||||||
|
requireProcessDependency(
|
||||||
|
evelynPerpsService,
|
||||||
|
EVELYN_MISSION_CONTROL_ID,
|
||||||
|
EVELYN_PERPS_SERVICE_ID),
|
||||||
requireProcessDependency(
|
requireProcessDependency(
|
||||||
javaFxRuntimeService,
|
javaFxRuntimeService,
|
||||||
EVELYN_MISSION_CONTROL_ID,
|
EVELYN_MISSION_CONTROL_ID,
|
||||||
@@ -421,6 +433,7 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
Map.entry(EVELYN_STATUS_TEST_ID, this::constructEvelynStatusTestProcess),
|
Map.entry(EVELYN_STATUS_TEST_ID, this::constructEvelynStatusTestProcess),
|
||||||
Map.entry(EVELYN_LIQUIDITY_PROD_ID, this::constructEvelynLiquidityProdProcess),
|
Map.entry(EVELYN_LIQUIDITY_PROD_ID, this::constructEvelynLiquidityProdProcess),
|
||||||
Map.entry(EVELYN_IOU_BURNER_PROD_ID, this::constructEvelynIOUBurnerProdProcess),
|
Map.entry(EVELYN_IOU_BURNER_PROD_ID, this::constructEvelynIOUBurnerProdProcess),
|
||||||
|
Map.entry(EVELYN_PERPS_SERVICE_ID, this::constructEvelynPerpsProcess),
|
||||||
Map.entry(JAVAFX_RUNTIME_SERVICE_ID, this::constructJavaFxRuntimeProcess),
|
Map.entry(JAVAFX_RUNTIME_SERVICE_ID, this::constructJavaFxRuntimeProcess),
|
||||||
Map.entry(EVELYN_MISSION_CONTROL_ID, this::constructMissionControlProcess),
|
Map.entry(EVELYN_MISSION_CONTROL_ID, this::constructMissionControlProcess),
|
||||||
Map.entry(PROCESS_MANAGER_ID, this::constructProcessManagerProcess)
|
Map.entry(PROCESS_MANAGER_ID, this::constructProcessManagerProcess)
|
||||||
@@ -538,6 +551,8 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
new NenjimComponentId("evelyn.liquidity.prod");
|
new NenjimComponentId("evelyn.liquidity.prod");
|
||||||
private static final NenjimComponentId EVELYN_MISSION_CONTROL_ID =
|
private static final NenjimComponentId EVELYN_MISSION_CONTROL_ID =
|
||||||
new NenjimComponentId("evelyn.mission-control.default");
|
new NenjimComponentId("evelyn.mission-control.default");
|
||||||
|
private static final NenjimComponentId EVELYN_PERPS_SERVICE_ID =
|
||||||
|
new NenjimComponentId("evelyn.perps.default");
|
||||||
private static final NenjimComponentId JAVAFX_RUNTIME_SERVICE_ID =
|
private static final NenjimComponentId JAVAFX_RUNTIME_SERVICE_ID =
|
||||||
new NenjimComponentId("nenjim.javafx-runtime.service");
|
new NenjimComponentId("nenjim.javafx-runtime.service");
|
||||||
private static final NenjimComponentId PROCESS_MANAGER_ID =
|
private static final NenjimComponentId PROCESS_MANAGER_ID =
|
||||||
@@ -576,6 +591,7 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
private JupiterPerpsAlarm jupiterPerpsAlarm;
|
private JupiterPerpsAlarm jupiterPerpsAlarm;
|
||||||
private EvelynStatusService evelynStatusServiceProd;
|
private EvelynStatusService evelynStatusServiceProd;
|
||||||
private EvelynStatusService evelynStatusServiceTest;
|
private EvelynStatusService evelynStatusServiceTest;
|
||||||
|
private EvelynPerpsService evelynPerpsService;
|
||||||
private JupiterSwapService jupiterSwapService;
|
private JupiterSwapService jupiterSwapService;
|
||||||
private BoundNotificationService discordAssetAZTokenNotificationService;
|
private BoundNotificationService discordAssetAZTokenNotificationService;
|
||||||
private EvelynIOUBurnerService evelynIOUBurnerServiceProd;
|
private EvelynIOUBurnerService evelynIOUBurnerServiceProd;
|
||||||
|
|||||||
Reference in New Issue
Block a user