Implement live Evelyn Price Index in Evelyn Mission Control #64

Closed
opened 2026-08-08 17:50:44 +02:00 by minimons · 0 comments
Owner

Background

The Overview tab in Evelyn Mission Control displays indexes describing how well-balanced Evelyn is. The desired value for every index is 0.

This task implements the first real index: Evelyn Price Index.

Use commit [cf603b4](https://github.com/r35157/com_r35157_nenjim-hubd-impl_ref/commit/cf603b416e400027fa31c86300778028105584c3) as the starting point.

The AssetAZ Ticker and RaydiumPoolPriceSource can already provide the real EVE/USDT market price. The current EMC implementation still uses hardcoded EvelynStatusIndexPoint data and creates a static chart snapshot.

Price-index calculation

The expected EVE price starts at:

  • Price: 15.00 USD
  • Time: 2026-08-01T00:00
  • Time zone: Europe/Copenhagen
  • Corresponding instant: 2026-07-31T22:00:00Z

The expected price grows continuously along an exponential curve that produces exactly 20% growth per 365-day year:

expectedPrice =
    15 * 1.20 ^ (elapsedSeconds / 31_536_000)

elapsedSeconds is the elapsed duration from the configured start instant to the sampling instant. This is exponential growth, not linear interpolation.

Examples:

At the start:
expectedPrice = 15

After exactly 365 days:
expectedPrice = 18

After 18 days, 3 hours, 8 minutes and 14 seconds:
expectedPrice ≈ 15.136464436276

The Evelyn Price Index is:

evelynPriceIndex =
    ((actualPrice / expectedPrice) - 1) * 10

Examples:

actualPrice == expectedPrice       -> index  0
actualPrice is 10% above expected  -> index  1
actualPrice is 10% below expected  -> index -1
actualPrice is twice expected      -> index 10
actualPrice is half of expected    -> index -5

Use adequate decimal precision and an explicit MathContext where division requires it. Avoid arbitrary rounding of the value stored in the history or supplied to the chart.

The expected-price and index calculations must be isolated from the JavaFX presentation code and be deterministic/testable using an explicit Instant or injected Clock.

Calls before the configured start time do not need to be supported in version 1.

Obtain the actual price from AssetAZ Ticker

The actual price must come from:

TickerService.getLatestPrice(eveUsdtTradingPair)

Use the existing canonical EVE and USDT identities from the Currency Identity Service. Do not identify currencies using raw symbol strings or duplicate UUIDs/mint addresses.

The value to use is the price from the returned PriceObservation/AssetPrice.

For version 1, the EVE/USDT quote is treated directly as the USD price:

1 USDT == 1 USD

Currency conversion and depeg handling are outside this task.

EMC and Evelyn must not communicate directly with Raydium. The dependency direction must remain:

Raydium PriceSource -> TickerService -> Evelyn -> Evelyn Mission Control

The calculation and in-memory status-index history belong to Evelyn-owned code. EMC is responsible for visualizing that data, not for implementing the financial formula or accessing Raydium directly.

Sampling and in-memory history

Replace the hardcoded history currently returned by EvelynImpl.

For each Evelyn environment:

  • The status-index history starts empty.
  • Attempt to add the first point immediately after the required services and EMC have started.
  • Add another point approximately once per minute.
  • Use fixed-delay or equivalent non-overlapping scheduling; do not create catch-up bursts.
  • The point timestamp represents the sampling instant used for the expected-price calculation.
  • Add exactly one point for each successful sample.
  • Keep the points ordered from oldest to newest.
  • Keep all points for the lifetime of the current process; no runtime size limit is required in version 1.
  • Keep Production and Test histories as separate collections and separate chart series. They may contain identical values because they use the same market price and expected-price curve.

The history must exist only in memory:

  • Do not persist EvelynStatusIndexPoint data.
  • Do not restore or backfill index points from earlier runs.
  • Restarting the process must produce an empty chart that begins collecting new points.
  • Do not change the Ticker’s existing persistence behavior. Ticker price history and Evelyn index history are separate concepts.

getStatusIndexHistory() must return a safe, non-null snapshot and must remain safe while new points are being added.

Temporary price unavailability

Ticker startup and Raydium’s first asynchronous poll may race with the first index sample.

If TickerService.getLatestPrice(...) cannot yet return a price, or if one sampling attempt otherwise fails:

  • Do not insert a fake or zero-valued price-index point.
  • Do not crash Evelyn, EMC, JavaFX, or the Ticker.
  • Log enough context to diagnose the failed sample.
  • Retry normally at the next interval.

A temporary failure must not cancel future sampling.

Preserve the six-index API for future work

EvelynStatusIndexPoint currently contains all six planned indexes. Do not remove the five future fields.

For the real points created in this version:

  • Populate evelynPriceIndex with the calculated value.
  • Use BigDecimal.ZERO as temporary values for the other five fields.

In EvelynMissionControlImpl, only the following series must currently be enabled:

Evelyn Price Index

The five future series must not be deleted. Disable them by commenting out their existing chart.getData().add(...) statements:

EVE_SYRUP Pool Depth Index
EVE_SYRUP Pool Balance Index
AAZDKK_USDT Pool Balance Index
AAZDKK_USDT Pool Price Index
AAZDKK_USDT Pool Depth Index

Also disable/comment out their contributions to Y-axis bound calculation, so only the enabled Evelyn Price Index controls the visible range.

The API fields and the commented chart scaffolding must remain available for later implementation.

Live EMC chart behavior

The Production and Test Overview charts must update while EMC remains open. Reopening the window or restarting EMC must not be required to see new points.

The chart must:

  • Start correctly with an empty history.
  • Contain one enabled series named Evelyn Price Index.
  • Add successful samples on the JavaFX Application Thread.
  • Expand/update the timestamp axis when points are added.
  • Calculate the Y-axis range from the enabled price-index series.
  • Keep the Y-axis symmetric around 0.
  • Always display 0 as the desired state.
  • Continue using a reasonable default symmetric range when the history is empty or every index value is zero.
  • Avoid leaking scheduler/timer resources after the corresponding EMC window or sampling lifecycle has ended.

Do not allow background threads to mutate JavaFX chart objects directly.

NenjimHub composition and safety

In NenjimHubImpl, the services required by this feature are currently commented out deliberately.

Enable only the wiring necessary for:

  • CurrencyIdentityService
  • SolanaBlockChain
  • Raydium
  • The existing real EVE/USDT RaydiumPoolPriceSource
  • TickerService

Start the Ticker before Evelyn begins sampling and pass the public TickerService dependency and canonical EVE/USDT trading pair through the composition graph.

The unrelated HardcodedPriceSource is not required for this feature and may remain disabled.

Keep all unrelated services commented out, including alarms, Nenjim Composer, Nenjim Process Manager, Nenjim Test Tool, Soda Task Manager and Suwimo Client. Do not accidentally activate trading, alarms, transaction execution, or other side effects.

Continue relying on the existing Ticker history-file activation contract. Do not create, migrate, or change Ticker history files as part of this task.

OpenSpec

Create an OpenSpec change for this issue.

Update the existing evelyn-status-index-history capability so it no longer claims that all six lines are currently displayed. The canonical behavior after this task is:

  • The six typed fields remain part of EvelynStatusIndexPoint.
  • Only Evelyn Price Index is currently calculated and displayed.
  • The other five graph series remain present in source as disabled future scaffolding.
  • Price-index points are sampled during the current run and kept only in memory.
  • The expected-price curve, index formula, Ticker dependency and failure behavior are specified.

Do not change the generic Ticker or Raydium PriceSource contracts unless a real implementation blocker is discovered.

Verification

Add focused deterministic verification for at least:

  • Expected price at the start instant is 15.
  • Expected price after one 365-day year is 18.
  • The 18-day, 3-hour, 8-minute and 14-second example is approximately 15.136464436276.
  • Equal actual and expected price gives index 0.
  • A 10% premium gives index 1.
  • A 10% discount gives index -1.
  • Double price gives index 10.
  • Missing initial Ticker data creates no point and does not stop later sampling.
  • Histories start empty and remain separate for Production and Test.
  • Only Evelyn Price Index affects the graph’s Y-axis bounds.
  • An empty chart can be created and later updated successfully.

Use a stub/fake TickerService and controllable time for deterministic verification. Automated verification must not access Raydium or the network.

Finally:

  • Compile main and test source sets.
  • Run relevant tests or deterministic harnesses.
  • Run strict OpenSpec validation.
  • Run git diff --check.
  • Review the final diff for accidentally enabled services and unrelated changes.

Out of scope

  • Persisting or restoring Evelyn index history
  • Backfilling the graph from Ticker history
  • Implementing the other five indexes
  • Enabling their five graph series
  • Configurable start price, start time, annual growth or sampling interval
  • USDT/USD conversion or depeg handling
  • Raydium streaming
  • Changes to the existing Raydium polling implementation
  • Changes to generic Ticker persistence or PriceSource discovery
  • Trading actions, alarms or automatic rebalancing
## Background The `Overview` tab in Evelyn Mission Control displays indexes describing how well-balanced Evelyn is. The desired value for every index is `0`. This task implements the first real index: **Evelyn Price Index**. Use commit [`[cf603b4](https://github.com/r35157/com_r35157_nenjim-hubd-impl_ref/commit/cf603b416e400027fa31c86300778028105584c3)`](https://github.com/r35157/com_r35157_nenjim-hubd-impl_ref/commit/cf603b416e400027fa31c86300778028105584c3) as the starting point. The AssetAZ Ticker and `RaydiumPoolPriceSource` can already provide the real EVE/USDT market price. The current EMC implementation still uses hardcoded `EvelynStatusIndexPoint` data and creates a static chart snapshot. ## Price-index calculation The expected EVE price starts at: * Price: `15.00 USD` * Time: `2026-08-01T00:00` * Time zone: `Europe/Copenhagen` * Corresponding instant: `2026-07-31T22:00:00Z` The expected price grows continuously along an exponential curve that produces exactly 20% growth per 365-day year: ```text expectedPrice = 15 * 1.20 ^ (elapsedSeconds / 31_536_000) ``` `elapsedSeconds` is the elapsed duration from the configured start instant to the sampling instant. This is exponential growth, not linear interpolation. Examples: ```text At the start: expectedPrice = 15 After exactly 365 days: expectedPrice = 18 After 18 days, 3 hours, 8 minutes and 14 seconds: expectedPrice ≈ 15.136464436276 ``` The Evelyn Price Index is: ```text evelynPriceIndex = ((actualPrice / expectedPrice) - 1) * 10 ``` Examples: ```text actualPrice == expectedPrice -> index 0 actualPrice is 10% above expected -> index 1 actualPrice is 10% below expected -> index -1 actualPrice is twice expected -> index 10 actualPrice is half of expected -> index -5 ``` Use adequate decimal precision and an explicit `MathContext` where division requires it. Avoid arbitrary rounding of the value stored in the history or supplied to the chart. The expected-price and index calculations must be isolated from the JavaFX presentation code and be deterministic/testable using an explicit `Instant` or injected `Clock`. Calls before the configured start time do not need to be supported in version 1. ## Obtain the actual price from AssetAZ Ticker The actual price must come from: ```java TickerService.getLatestPrice(eveUsdtTradingPair) ``` Use the existing canonical EVE and USDT identities from the Currency Identity Service. Do not identify currencies using raw symbol strings or duplicate UUIDs/mint addresses. The value to use is the price from the returned `PriceObservation`/`AssetPrice`. For version 1, the EVE/USDT quote is treated directly as the USD price: ```text 1 USDT == 1 USD ``` Currency conversion and depeg handling are outside this task. EMC and Evelyn must not communicate directly with Raydium. The dependency direction must remain: ```text Raydium PriceSource -> TickerService -> Evelyn -> Evelyn Mission Control ``` The calculation and in-memory status-index history belong to Evelyn-owned code. EMC is responsible for visualizing that data, not for implementing the financial formula or accessing Raydium directly. ## Sampling and in-memory history Replace the hardcoded history currently returned by `EvelynImpl`. For each Evelyn environment: * The status-index history starts empty. * Attempt to add the first point immediately after the required services and EMC have started. * Add another point approximately once per minute. * Use fixed-delay or equivalent non-overlapping scheduling; do not create catch-up bursts. * The point timestamp represents the sampling instant used for the expected-price calculation. * Add exactly one point for each successful sample. * Keep the points ordered from oldest to newest. * Keep all points for the lifetime of the current process; no runtime size limit is required in version 1. * Keep Production and Test histories as separate collections and separate chart series. They may contain identical values because they use the same market price and expected-price curve. The history must exist only in memory: * Do not persist `EvelynStatusIndexPoint` data. * Do not restore or backfill index points from earlier runs. * Restarting the process must produce an empty chart that begins collecting new points. * Do not change the Ticker’s existing persistence behavior. Ticker price history and Evelyn index history are separate concepts. `getStatusIndexHistory()` must return a safe, non-null snapshot and must remain safe while new points are being added. ## Temporary price unavailability Ticker startup and Raydium’s first asynchronous poll may race with the first index sample. If `TickerService.getLatestPrice(...)` cannot yet return a price, or if one sampling attempt otherwise fails: * Do not insert a fake or zero-valued price-index point. * Do not crash Evelyn, EMC, JavaFX, or the Ticker. * Log enough context to diagnose the failed sample. * Retry normally at the next interval. A temporary failure must not cancel future sampling. ## Preserve the six-index API for future work `EvelynStatusIndexPoint` currently contains all six planned indexes. Do not remove the five future fields. For the real points created in this version: * Populate `evelynPriceIndex` with the calculated value. * Use `BigDecimal.ZERO` as temporary values for the other five fields. In `EvelynMissionControlImpl`, only the following series must currently be enabled: ```text Evelyn Price Index ``` The five future series must not be deleted. Disable them by commenting out their existing `chart.getData().add(...)` statements: ```text EVE_SYRUP Pool Depth Index EVE_SYRUP Pool Balance Index AAZDKK_USDT Pool Balance Index AAZDKK_USDT Pool Price Index AAZDKK_USDT Pool Depth Index ``` Also disable/comment out their contributions to Y-axis bound calculation, so only the enabled Evelyn Price Index controls the visible range. The API fields and the commented chart scaffolding must remain available for later implementation. ## Live EMC chart behavior The Production and Test `Overview` charts must update while EMC remains open. Reopening the window or restarting EMC must not be required to see new points. The chart must: * Start correctly with an empty history. * Contain one enabled series named `Evelyn Price Index`. * Add successful samples on the JavaFX Application Thread. * Expand/update the timestamp axis when points are added. * Calculate the Y-axis range from the enabled price-index series. * Keep the Y-axis symmetric around `0`. * Always display `0` as the desired state. * Continue using a reasonable default symmetric range when the history is empty or every index value is zero. * Avoid leaking scheduler/timer resources after the corresponding EMC window or sampling lifecycle has ended. Do not allow background threads to mutate JavaFX chart objects directly. ## NenjimHub composition and safety In `NenjimHubImpl`, the services required by this feature are currently commented out deliberately. Enable only the wiring necessary for: * `CurrencyIdentityService` * `SolanaBlockChain` * `Raydium` * The existing real EVE/USDT `RaydiumPoolPriceSource` * `TickerService` Start the Ticker before Evelyn begins sampling and pass the public `TickerService` dependency and canonical EVE/USDT trading pair through the composition graph. The unrelated `HardcodedPriceSource` is not required for this feature and may remain disabled. Keep all unrelated services commented out, including alarms, Nenjim Composer, Nenjim Process Manager, Nenjim Test Tool, Soda Task Manager and Suwimo Client. Do not accidentally activate trading, alarms, transaction execution, or other side effects. Continue relying on the existing Ticker history-file activation contract. Do not create, migrate, or change Ticker history files as part of this task. ## OpenSpec Create an OpenSpec change for this issue. Update the existing `evelyn-status-index-history` capability so it no longer claims that all six lines are currently displayed. The canonical behavior after this task is: * The six typed fields remain part of `EvelynStatusIndexPoint`. * Only Evelyn Price Index is currently calculated and displayed. * The other five graph series remain present in source as disabled future scaffolding. * Price-index points are sampled during the current run and kept only in memory. * The expected-price curve, index formula, Ticker dependency and failure behavior are specified. Do not change the generic Ticker or Raydium PriceSource contracts unless a real implementation blocker is discovered. ## Verification Add focused deterministic verification for at least: * Expected price at the start instant is `15`. * Expected price after one 365-day year is `18`. * The 18-day, 3-hour, 8-minute and 14-second example is approximately `15.136464436276`. * Equal actual and expected price gives index `0`. * A 10% premium gives index `1`. * A 10% discount gives index `-1`. * Double price gives index `10`. * Missing initial Ticker data creates no point and does not stop later sampling. * Histories start empty and remain separate for Production and Test. * Only Evelyn Price Index affects the graph’s Y-axis bounds. * An empty chart can be created and later updated successfully. Use a stub/fake `TickerService` and controllable time for deterministic verification. Automated verification must not access Raydium or the network. Finally: * Compile main and test source sets. * Run relevant tests or deterministic harnesses. * Run strict OpenSpec validation. * Run `git diff --check`. * Review the final diff for accidentally enabled services and unrelated changes. ## Out of scope * Persisting or restoring Evelyn index history * Backfilling the graph from Ticker history * Implementing the other five indexes * Enabling their five graph series * Configurable start price, start time, annual growth or sampling interval * USDT/USD conversion or depeg handling * Raydium streaming * Changes to the existing Raydium polling implementation * Changes to generic Ticker persistence or PriceSource discovery * Trading actions, alarms or automatic rebalancing
minimons added the enhancement label 2026-08-08 17:50:44 +02:00
minimons self-assigned this 2026-08-08 17:50:44 +02:00
minimons added this to the Evelyn project 2026-08-08 17:50:44 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: r35157/com_r35157_nenjim-hubd-impl_ref#64