diff --git a/openspec/changes/archive/2026-08-08-64-implement-live-evelyn-price-index/.openspec.yaml b/openspec/changes/archive/2026-08-08-64-implement-live-evelyn-price-index/.openspec.yaml new file mode 100644 index 0000000..913564e --- /dev/null +++ b/openspec/changes/archive/2026-08-08-64-implement-live-evelyn-price-index/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-08-08 diff --git a/openspec/changes/archive/2026-08-08-64-implement-live-evelyn-price-index/design.md b/openspec/changes/archive/2026-08-08-64-implement-live-evelyn-price-index/design.md new file mode 100644 index 0000000..e22faaa --- /dev/null +++ b/openspec/changes/archive/2026-08-08-64-implement-live-evelyn-price-index/design.md @@ -0,0 +1,50 @@ +## Context + +See `proposal.md` for motivation. Evelyn currently returns hardcoded history and EMC copies it once while constructing six static series. The existing Ticker already supplies persisted latest EVE/USDT observations through its public API. The baseline deliberately disables all NenjimHub autorun services, so this change must explicitly reactivate only the dependency chain needed for the live chart. + +## Goals / Non-Goals + +**Goals:** + +- Keep financial calculation, sampling, and process-local history in Evelyn-owned code. +- Keep JavaFX chart mutation and visualization lifecycle in EMC-owned code. +- Make formula and scheduler behavior deterministic using explicit instants, an injected clock, and injectable delay units. +- Preserve all six typed index fields while enabling only the price-index chart series. +- Retain the safety posture of the baseline composition root. + +**Non-Goals:** + +- Ticker/Raydium contract changes, index persistence/backfill, other five index calculations, conversions, configuration, streaming, trading, alarms, unrelated service activation, or automated unit-test coverage in this change. + +## Decisions + +### Separate the pure formula from sampling + +An Evelyn-owned calculator accepts explicit `BigDecimal` prices and `Instant` values. It uses the Copenhagen start configuration resolved to the fixed instant `2026-07-31T22:00:00Z`, a 365-day seconds denominator, and an explicit high-precision `MathContext`. Fractional exponentiation is isolated behind the calculator rather than embedded in JavaFX or scheduler code. This makes all reference examples deterministic. Direct calculation in EMC was rejected because presentation must not own financial behavior. + +### Make sampling part of the Evelyn lifecycle + +The Evelyn API exposes start and stop operations for the complete Evelyn lifecycle rather than exposing sampling controls to presentation code. The reference implementation receives only public `TickerService`, the canonical expected trading pair, a clock, and delay configuration. Starting each instance creates its daemon single-thread scheduler using fixed delay with zero initial delay. Successful attempts append under a lock; snapshot reads use `List.copyOf` under the same lock. Exceptions are caught at the task boundary so future attempts survive. Stopping is idempotent, releases the owned executor, and discards that instance's process-local history. A global scheduler, an EMC-owned scheduler, or shared Production/Test history was rejected because environments and lifecycles must remain independent. + +### Let EMC incrementally reconcile snapshots on the JavaFX thread + +Each environment chart keeps its own enabled series and a cache used only to optimize rendering. Opening or reopening EMC reconstructs both charts and immediately reconciles each complete authoritative Evelyn snapshot before starting a JavaFX `Timeline` for incremental refresh. Prefix mismatches also force a complete series rebuild, so a stale count cannot hide replaced history. Empty and single-point histories receive non-degenerate timestamp bounds; Y bounds use only absolute Evelyn Price Index values and fall back to a symmetric default. The five existing series-add and bound-contribution statements remain commented scaffolding. Background observer callbacks were rejected because they complicate thread ownership and can mutate JavaFX objects unsafely. + +### Keep EMC lifecycle presentation-only + +NenjimHub starts both Evelyn instances after Ticker and before opening EMC. EMC only reads their histories and owns its JavaFX refresh timeline. Hiding the window stops that timeline but leaves Evelyn sampling and history untouched. Reopening the same EMC instance creates fresh chart state from the complete current snapshots, so points collected while hidden remain visible. Evelyn sampling stops and its in-memory history is discarded only when the corresponding Evelyn instance is stopped. + +### Enable only the required NenjimHub dependency chain + +`NenjimHubImpl` creates one CIS, Solana implementation, Raydium implementation, the existing pool-specific EVE/USDT PriceSource, and Ticker; it starts Ticker before constructing and starting two Evelyn instances with the public Ticker and canonical pair, then starts EMC. The hardcoded source, alarm, Composer, Process Manager, Test Tool, Soda Task Manager, Suwimo Client, and all trading behavior remain disabled. Existing-file-only Ticker activation remains unchanged. + +## Risks / Trade-offs + +- [Fractional exponentiation ultimately uses finite floating-point transcendental support] → isolate conversion and retain DECIMAL128 arithmetic around it; the deterministic seam remains available for later focused verification. +- [EMC refresh can observe several newly sampled points at once] → reconcile authoritative snapshots against the rendered prefix on the JavaFX thread and preserve oldest-to-newest order. +- [Ticker has no active EVE/USDT history or has not completed its first poll] → log and skip the point; fixed-delay sampling retries without fabricating data. +- [EMC can be hidden while samples continue] → keep history authoritative in Evelyn and reconstruct fresh chart state from complete snapshots whenever EMC is reopened. + +## Migration Plan + +Deploy with the existing manually activated EVE/USDT Ticker history file. On process start, histories are intentionally empty. Rollback restores the disabled autorun composition and static presentation; no data migration is needed because index history is never persisted. diff --git a/openspec/changes/archive/2026-08-08-64-implement-live-evelyn-price-index/proposal.md b/openspec/changes/archive/2026-08-08-64-implement-live-evelyn-price-index/proposal.md new file mode 100644 index 0000000..75bcaf8 --- /dev/null +++ b/openspec/changes/archive/2026-08-08-64-implement-live-evelyn-price-index/proposal.md @@ -0,0 +1,26 @@ +## Why + +Evelyn Mission Control currently renders a static six-series chart from hardcoded data, so it does not describe Evelyn's live market-price balance. Evelyn needs to calculate and retain the first real status index from AssetAZ Ticker while EMC updates safely during the current process lifetime. + +## What Changes + +- Replace hardcoded Evelyn status-index points with separate, thread-safe, in-memory Production and Test histories sampled for the complete lifecycle of each started Evelyn instance. +- Calculate the expected exponential EVE price and Evelyn Price Index deterministically from an explicit sampling instant and the latest canonical EVE/USDT Ticker observation. +- Isolate temporary Ticker/sample failures so no fake point is created and later fixed-delay samples continue. +- Make EMC's Production and Test charts reconstruct the complete current Evelyn snapshots when opened or reopened and update live on the JavaFX Application Thread, with only Evelyn Price Index enabled and controlling symmetric bounds. +- Compose only CIS, Solana, Raydium, the real EVE/USDT PriceSource, Ticker, Evelyn, and EMC in NenjimHub; leave all unrelated and safety-sensitive services disabled. +- Keep calculation, timing, sampling-delay, and chart-update seams deterministic so focused automated verification can be added later; unit tests are deliberately deferred from this change. + +## Capabilities + +### New Capabilities + +None. + +### Modified Capabilities + +- `evelyn-status-index-history`: Replaces hardcoded six-line snapshots with live in-memory Evelyn Price Index sampling, formula, failure handling, separate environment histories, and one enabled live chart series. + +## Impact + +The change affects the Evelyn API/reference implementation, EMC chart lifecycle and rendering, and temporary NenjimHub composition. It depends on the existing public Ticker and canonical currency identities; generic Ticker and Raydium PriceSource contracts and persistence remain unchanged. diff --git a/openspec/changes/archive/2026-08-08-64-implement-live-evelyn-price-index/specs/evelyn-status-index-history/spec.md b/openspec/changes/archive/2026-08-08-64-implement-live-evelyn-price-index/specs/evelyn-status-index-history/spec.md new file mode 100644 index 0000000..7a45a3b --- /dev/null +++ b/openspec/changes/archive/2026-08-08-64-implement-live-evelyn-price-index/specs/evelyn-status-index-history/spec.md @@ -0,0 +1,71 @@ +## ADDED Requirements + +### Requirement: Evelyn calculates the live price index +Evelyn SHALL obtain the actual EVE/USDT price from the latest AssetAZ Ticker observation for the canonical EVE/USDT trading pair. It SHALL treat USDT as USD for this calculation and SHALL NOT access Raydium directly. At sampling instant `t`, on or after `2026-07-31T22:00:00Z`, the expected price SHALL be `15 × 1.20^(elapsedSeconds / 31,536,000)` and the Evelyn Price Index SHALL be `((actualPrice / expectedPrice) - 1) × 10`, calculated with adequate decimal precision and without arbitrary history rounding. + +#### Scenario: Calculate expected price at the configured start +- **WHEN** the sampling instant is `2026-07-31T22:00:00Z` +- **THEN** the expected EVE price is exactly 15 USD + +#### Scenario: Calculate expected price after one year +- **WHEN** exactly 31,536,000 seconds have elapsed since the configured start +- **THEN** the expected EVE price is exactly 18 USD + +#### Scenario: Calculate representative fractional-year growth +- **WHEN** 18 days, 3 hours, 8 minutes, and 14 seconds have elapsed +- **THEN** the expected EVE price is approximately `15.136464436276` USD + +#### Scenario: Calculate index from actual price +- **WHEN** actual price equals expected price, is 10 percent above, is 10 percent below, is double, or is half the expected price +- **THEN** the respective index is 0, 1, -1, 10, or -5 + +### Requirement: Evelyn samples without overlapping or catch-up attempts +Each started Evelyn environment SHALL own its sampling executor, attempt its first sample immediately, and make subsequent attempts approximately one minute after the preceding attempt completes. Sampling SHALL remain active for the Evelyn lifecycle independently of whether Mission Control is open. Evelyn SHALL add exactly one timestamped point per successful attempt and SHALL isolate a failed or temporarily unavailable Ticker lookup without adding a fake point, terminating Evelyn, or cancelling the next attempt. Stopping Evelyn SHALL terminate its sampling executor. + +#### Scenario: Initial Ticker data is unavailable +- **WHEN** the immediate sampling attempt cannot obtain a latest EVE/USDT price +- **THEN** no point is added, diagnostic context is logged, and the next fixed-delay attempt remains scheduled + +#### Scenario: A later sample succeeds +- **WHEN** a later attempt obtains a latest EVE/USDT price +- **THEN** exactly one point is appended using the sampling instant for both expected-price calculation and point timestamp + +## MODIFIED Requirements + +### Requirement: Evelyn provides historical status-index measurements +Each Evelyn instance SHALL begin with an empty, process-local in-memory history and expose a safe, non-null snapshot of typed measurement points ordered from oldest to newest while sampling may continue concurrently. Each successful point SHALL contain its sampling timestamp, the calculated Evelyn Price Index, and `BigDecimal.ZERO` placeholders for EVE_SYRUP Pool Depth Index, EVE_SYRUP Pool Balance Index, AAZDKK_USDT Pool Balance Index, AAZDKK_USDT Pool Price Index, and AAZDKK_USDT Pool Depth Index. Production and Test SHALL use separate history collections, and no point SHALL be persisted, restored, or backfilled. + +#### Scenario: Status-index history is requested during sampling +- **WHEN** a caller requests an Evelyn environment's status-index history while points may be appended +- **THEN** it receives a safe, non-null, oldest-to-newest snapshot containing all six typed fields + +#### Scenario: A process starts two Evelyn environments +- **WHEN** Production and Test Evelyn instances are created for a new process +- **THEN** both histories start empty and remain separate even if their calculated values are identical + +#### Scenario: A real point is sampled +- **WHEN** Evelyn appends a successful price-index point +- **THEN** its Evelyn Price Index contains the calculated value and each of the other five fields contains zero + +#### Scenario: An Evelyn instance is stopped +- **WHEN** a Production or Test Evelyn instance is stopped +- **THEN** its sampling executor terminates and its complete process-local history is discarded without affecting the other environment + +### Requirement: Evelyn Mission Control visualizes status-index measurements +Evelyn Mission Control SHALL display separate live Production and Test Overview charts from their corresponding authoritative Evelyn snapshots. Each chart SHALL contain one enabled series named `Evelyn Price Index`; the five future series and their typed fields SHALL remain available as disabled source scaffolding. Opening or reopening EMC SHALL immediately reconstruct each chart from the complete current snapshot before periodically refreshing it. Successful points SHALL be added to chart objects only on the JavaFX Application Thread while the window remains open. Timestamp bounds SHALL expand as points arrive, and only enabled Evelyn Price Index values SHALL determine a dynamic Y-axis range that is symmetric around and always displays zero, using a reasonable default range for empty or all-zero history. Hiding or closing EMC SHALL release only EMC's JavaFX refresh resources and SHALL NOT start, stop, or clear either Evelyn environment. + +#### Scenario: Production and Test Overviews start empty +- **WHEN** Mission Control opens with two newly created Evelyn environments +- **THEN** it displays two separate empty one-series charts with valid timestamp axes and symmetric Y-axes containing zero + +#### Scenario: A successful point appears while EMC is open +- **WHEN** an Evelyn history receives a new successful point +- **THEN** the corresponding chart adds it on the JavaFX Application Thread and updates its timestamp and symmetric price-index bounds without reopening EMC + +#### Scenario: EMC reopens after collecting hidden-window samples +- **WHEN** the same EMC instance is reopened after Evelyn collected additional points while its window was hidden +- **THEN** each chart is reconstructed immediately from its environment's complete current history without missing points because of stale rendering state + +#### Scenario: Future fields contain larger values +- **WHEN** any disabled future-index field has a greater absolute value than Evelyn Price Index +- **THEN** that disabled value does not affect the visible Y-axis bounds diff --git a/openspec/changes/archive/2026-08-08-64-implement-live-evelyn-price-index/tasks.md b/openspec/changes/archive/2026-08-08-64-implement-live-evelyn-price-index/tasks.md new file mode 100644 index 0000000..62273dc --- /dev/null +++ b/openspec/changes/archive/2026-08-08-64-implement-live-evelyn-price-index/tasks.md @@ -0,0 +1,24 @@ +## 1. Evelyn Calculation and History + +- [x] 1.1 Add the deterministic expected-price and Evelyn Price Index calculator with explicit precision and configured start instant. +- [x] 1.2 Replace hardcoded history with an empty thread-safe per-instance snapshot history and preserve all six typed fields using zero placeholders. +- [x] 1.3 Make restartable fixed-delay sampling and complete process-local history owned by each Evelyn lifecycle, with immediate first attempt, failure isolation, clean stop, and history discard on stop. + +## 2. Live Mission Control Chart + +- [x] 2.1 Refactor Production and Test Overview charts into separate live chart states that reconstruct the complete authoritative snapshot on every open and reconcile later snapshots on the JavaFX thread. +- [x] 2.2 Enable only Evelyn Price Index, retain the five future series and bound contributions as commented scaffolding, and calculate valid dynamic X and symmetric Y bounds from enabled data only. +- [x] 2.3 Keep EMC lifecycle presentation-only: hiding stops its JavaFX refresh, reopening rebuilds fresh chart state, and EMC never starts or stops Evelyn sampling. + +## 3. Safe NenjimHub Composition + +- [x] 3.1 Enable only CIS, Solana, Raydium, the EVE/USDT Raydium PriceSource, Ticker, two Evelyn environments, and EMC in the composition root. +- [x] 3.2 Start Ticker before constructing and independently starting both Evelyn lifecycles; pass public TickerService plus the canonical CIS-resolved EVE/USDT pair to each instance before starting EMC. +- [x] 3.3 Confirm HardcodedPriceSource and every unrelated alarm, trading, Composer, Process Manager, Test Tool, Soda Task Manager, and Suwimo service remain disabled. + +## 4. Specification and Final Checks + +- [x] 4.1 Update the canonical capability Purpose to stop claiming that all six indexes are currently visible. +- [x] 4.2 Compile main and test source sets without running unit tests; automated unit-test coverage is deliberately deferred. +- [x] 4.3 Run strict OpenSpec validation and `git diff --check`. +- [x] 4.4 Review the complete diff for unrelated changes, generic Ticker/Raydium changes, persistence changes, and accidentally enabled services. diff --git a/openspec/specs/evelyn-status-index-history/spec.md b/openspec/specs/evelyn-status-index-history/spec.md index 2637014..e0aa41d 100644 --- a/openspec/specs/evelyn-status-index-history/spec.md +++ b/openspec/specs/evelyn-status-index-history/spec.md @@ -2,24 +2,74 @@ ## Purpose -Provide typed historical Evelyn status measurements and make all six indexes visible over time in Mission Control. +Provide typed process-local Evelyn status measurements and visualize the currently implemented live indexes over time in Mission Control while preserving the six-index model for future work. ## Requirements -### Requirement: Evelyn provides historical status-index measurements -Evelyn SHALL expose a non-null list of typed measurement points ordered from oldest to newest. Each point SHALL contain a timestamp, Evelyn Price Index, EVE_SYRUP Pool Depth Index, EVE_SYRUP Pool Balance Index, AAZDKK_USDT Pool Balance Index, AAZDKK_USDT Pool Price Index, and AAZDKK_USDT Pool Depth Index; the list MAY be empty. +### Requirement: Evelyn calculates the live price index +Evelyn SHALL obtain the actual EVE/USDT price from the latest AssetAZ Ticker observation for the canonical EVE/USDT trading pair. It SHALL treat USDT as USD for this calculation and SHALL NOT access Raydium directly. At sampling instant `t`, on or after `2026-07-31T22:00:00Z`, the expected price SHALL be `15 × 1.20^(elapsedSeconds / 31,536,000)` and the Evelyn Price Index SHALL be `((actualPrice / expectedPrice) - 1) × 10`, calculated with adequate decimal precision and without arbitrary history rounding. -#### Scenario: Status-index history is requested -- **WHEN** a caller requests Evelyn's status-index history -- **THEN** Evelyn returns a non-null, oldest-to-newest list whose points contain a timestamp and all six index values +#### Scenario: Calculate expected price at the configured start +- **WHEN** the sampling instant is `2026-07-31T22:00:00Z` +- **THEN** the expected EVE price is exactly 15 USD + +#### Scenario: Calculate expected price after one year +- **WHEN** exactly 31,536,000 seconds have elapsed since the configured start +- **THEN** the expected EVE price is exactly 18 USD + +#### Scenario: Calculate representative fractional-year growth +- **WHEN** 18 days, 3 hours, 8 minutes, and 14 seconds have elapsed +- **THEN** the expected EVE price is approximately `15.136464436276` USD + +#### Scenario: Calculate index from actual price +- **WHEN** actual price equals expected price, is 10 percent above, is 10 percent below, is double, or is half the expected price +- **THEN** the respective index is 0, 1, -1, 10, or -5 + +### Requirement: Evelyn samples without overlapping or catch-up attempts +Each started Evelyn environment SHALL own its sampling executor, attempt its first sample immediately, and make subsequent attempts approximately one minute after the preceding attempt completes. Sampling SHALL remain active for the Evelyn lifecycle independently of whether Mission Control is open. Evelyn SHALL add exactly one timestamped point per successful attempt and SHALL isolate a failed or temporarily unavailable Ticker lookup without adding a fake point, terminating Evelyn, or cancelling the next attempt. Stopping Evelyn SHALL terminate its sampling executor. + +#### Scenario: Initial Ticker data is unavailable +- **WHEN** the immediate sampling attempt cannot obtain a latest EVE/USDT price +- **THEN** no point is added, diagnostic context is logged, and the next fixed-delay attempt remains scheduled + +#### Scenario: A later sample succeeds +- **WHEN** a later attempt obtains a latest EVE/USDT price +- **THEN** exactly one point is appended using the sampling instant for both expected-price calculation and point timestamp + +### Requirement: Evelyn provides historical status-index measurements +Each Evelyn instance SHALL begin with an empty, process-local in-memory history and expose a safe, non-null snapshot of typed measurement points ordered from oldest to newest while sampling may continue concurrently. Each successful point SHALL contain its sampling timestamp, the calculated Evelyn Price Index, and `BigDecimal.ZERO` placeholders for EVE_SYRUP Pool Depth Index, EVE_SYRUP Pool Balance Index, AAZDKK_USDT Pool Balance Index, AAZDKK_USDT Pool Price Index, and AAZDKK_USDT Pool Depth Index. Production and Test SHALL use separate history collections, and no point SHALL be persisted, restored, or backfilled. + +#### Scenario: Status-index history is requested during sampling +- **WHEN** a caller requests an Evelyn environment's status-index history while points may be appended +- **THEN** it receives a safe, non-null, oldest-to-newest snapshot containing all six typed fields + +#### Scenario: A process starts two Evelyn environments +- **WHEN** Production and Test Evelyn instances are created for a new process +- **THEN** both histories start empty and remain separate even if their calculated values are identical + +#### Scenario: A real point is sampled +- **WHEN** Evelyn appends a successful price-index point +- **THEN** its Evelyn Price Index contains the calculated value and each of the other five fields contains zero + +#### Scenario: An Evelyn instance is stopped +- **WHEN** a Production or Test Evelyn instance is stopped +- **THEN** its sampling executor terminates and its complete process-local history is discarded without affecting the other environment ### Requirement: Evelyn Mission Control visualizes status-index measurements -Evelyn Mission Control SHALL display historical measurements from separate Production and Test Evelyn references in their corresponding Overview tabs as six-line graphs, with timestamp on the X-axis and index value on the Y-axis. Each environment SHALL retrieve its own history and SHALL have a separate graph instance with a dynamic Y-axis range that is symmetric around zero and always displays zero as the desired state. The two histories MAY contain identical values but SHALL remain separate data sources. +Evelyn Mission Control SHALL display separate live Production and Test Overview charts from their corresponding authoritative Evelyn snapshots. Each chart SHALL contain one enabled series named `Evelyn Price Index`; the five future series and their typed fields SHALL remain available as disabled source scaffolding. Opening or reopening EMC SHALL immediately reconstruct each chart from the complete current snapshot before periodically refreshing it. Successful points SHALL be added to chart objects only on the JavaFX Application Thread while the window remains open. Timestamp bounds SHALL expand as points arrive, and only enabled Evelyn Price Index values SHALL determine a dynamic Y-axis range that is symmetric around and always displays zero, using a reasonable default range for empty or all-zero history. Hiding or closing EMC SHALL release only EMC's JavaFX refresh resources and SHALL NOT start, stop, or clear either Evelyn environment. -#### Scenario: Production and Test Overviews are created with history -- **WHEN** Mission Control creates the Production and Test Overview views -- **THEN** it retrieves history from the corresponding Production and Test Evelyn references and creates a separate six-line graph for each environment whose symmetric Y-axis contains zero +#### Scenario: Production and Test Overviews start empty +- **WHEN** Mission Control opens with two newly created Evelyn environments +- **THEN** it displays two separate empty one-series charts with valid timestamp axes and symmetric Y-axes containing zero -#### Scenario: An environment has empty history -- **WHEN** Mission Control creates an environment's Overview view and that environment's Evelyn history is empty -- **THEN** it displays an empty six-series graph for that environment whose Y-axis still contains zero without substituting the other environment's history +#### Scenario: A successful point appears while EMC is open +- **WHEN** an Evelyn history receives a new successful point +- **THEN** the corresponding chart adds it on the JavaFX Application Thread and updates its timestamp and symmetric price-index bounds without reopening EMC + +#### Scenario: EMC reopens after collecting hidden-window samples +- **WHEN** the same EMC instance is reopened after Evelyn collected additional points while its window was hidden +- **THEN** each chart is reconstructed immediately from its environment's complete current history without missing points because of stale rendering state + +#### Scenario: Future fields contain larger values +- **WHEN** any disabled future-index field has a greater absolute value than Evelyn Price Index +- **THEN** that disabled value does not affect the visible Y-axis bounds diff --git a/src/main/tjava/com/fanitas/evelyn/core/Evelyn.tjava b/src/main/tjava/com/fanitas/evelyn/core/Evelyn.tjava index de450ce..91e55a1 100644 --- a/src/main/tjava/com/fanitas/evelyn/core/Evelyn.tjava +++ b/src/main/tjava/com/fanitas/evelyn/core/Evelyn.tjava @@ -7,5 +7,9 @@ import java.util.List; public interface Evelyn { void executeService() throws Exception; + void start(); + + void stop(); + @NotNull List getStatusIndexHistory(); } diff --git a/src/main/tjava/com/fanitas/evelyn/core/impl/ref/EvelynImpl.tjava b/src/main/tjava/com/fanitas/evelyn/core/impl/ref/EvelynImpl.tjava index f24ebb6..6ae6235 100644 --- a/src/main/tjava/com/fanitas/evelyn/core/impl/ref/EvelynImpl.tjava +++ b/src/main/tjava/com/fanitas/evelyn/core/impl/ref/EvelynImpl.tjava @@ -13,14 +13,21 @@ import com.r35157.libs.solana.SolanaConstants; import com.r35157.libs.valuetypes.basic.AssetPrice; import com.r35157.libs.valuetypes.basic.MoneyAmount; import com.r35157.libs.valuetypes.basic.TradingPair; +import com.r35157.assetaz.services.ticker.PriceObservation; +import com.r35157.assetaz.services.ticker.TickerService; import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.math.BigDecimal; +import java.time.Clock; import java.time.Instant; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import static com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram.SPL_TOKEN_PROGRAM; import static com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram.TOKEN_2022_PROGRAM; @@ -38,7 +45,37 @@ public class EvelynImpl implements Evelyn { this.raydium = raydium; this.solanaChain = solanaChain; }*/ - public EvelynImpl() { + public EvelynImpl( + @NotNull TickerService tickerService, + @NotNull TradingPair eveUsdtTradingPair + ) { + this( + tickerService, + eveUsdtTradingPair, + Clock.systemUTC(), + SAMPLE_DELAY_MINUTES, + TimeUnit.MINUTES + ); + } + + EvelynImpl( + @NotNull TickerService tickerService, + @NotNull TradingPair eveUsdtTradingPair, + @NotNull Clock clock, + long sampleDelay, + @NotNull TimeUnit sampleDelayUnit + ) { + this.tickerService = Objects.requireNonNull(tickerService, "tickerService"); + this.eveUsdtTradingPair = Objects.requireNonNull( + eveUsdtTradingPair, + "eveUsdtTradingPair" + ); + this.clock = Objects.requireNonNull(clock, "clock"); + if (sampleDelay <= 0) { + throw new IllegalArgumentException("sampleDelay must be positive"); + } + this.sampleDelay = sampleDelay; + this.sampleDelayUnit = Objects.requireNonNull(sampleDelayUnit, "sampleDelayUnit"); } @Override @@ -47,36 +84,127 @@ public class EvelynImpl implements Evelyn { } @Override - public @NotNull List getStatusIndexHistory() { - return List.of( - statusIndexPoint("2026-08-02T12:00:00Z", "-0.20", "-0.35", "0.15", "0.30", "-0.10", "0.25"), - statusIndexPoint("2026-08-03T12:00:00Z", "-0.10", "-0.20", "0.10", "0.20", "-0.05", "0.15"), - statusIndexPoint("2026-08-04T12:00:00Z", "0.00", "-0.10", "0.05", "0.10", "0.00", "0.05"), - statusIndexPoint("2026-08-05T12:00:00Z", "0.10", "0.05", "-0.05", "0.00", "0.05", "-0.10"), - statusIndexPoint("2026-08-06T12:00:00Z", "0.20", "0.15", "-0.10", "-0.10", "0.10", "-0.20") + public synchronized void start() { + if (statusIndexScheduler != null || stoppingStatusIndexSampling) { + throw new IllegalStateException("Evelyn is already started"); + } + + ScheduledExecutorService newScheduler = Executors.newSingleThreadScheduledExecutor( + runnable -> { + Thread thread = new Thread(runnable, "evelyn-price-index-sampler"); + thread.setDaemon(true); + return thread; + } + ); + statusIndexScheduler = newScheduler; + newScheduler.scheduleWithFixedDelay( + this::sampleStatusIndexSafely, + 0, + sampleDelay, + sampleDelayUnit ); } - private static EvelynStatusIndexPoint statusIndexPoint( - String timestamp, - String evelynPriceIndex, - String eveSyrupPoolDepthIndex, - String eveSyrupPoolBalanceIndex, - String aazdkkUsdtPoolBalanceIndex, - String aazdkkUsdtPoolPriceIndex, - String aazdkkUsdtPoolDepthIndex - ) { - return new EvelynStatusIndexPoint( - Instant.parse(timestamp), - new BigDecimal(evelynPriceIndex), - new BigDecimal(eveSyrupPoolDepthIndex), - new BigDecimal(eveSyrupPoolBalanceIndex), - new BigDecimal(aazdkkUsdtPoolBalanceIndex), - new BigDecimal(aazdkkUsdtPoolPriceIndex), - new BigDecimal(aazdkkUsdtPoolDepthIndex) - ); + @Override + public void stop() { + ScheduledExecutorService schedulerToStop; + synchronized (this) { + if (statusIndexScheduler == null) { + clearStatusIndexHistory(); + return; + } + stoppingStatusIndexSampling = true; + schedulerToStop = statusIndexScheduler; + } + + schedulerToStop.shutdownNow(); + boolean terminated = false; + try { + terminated = schedulerToStop.awaitTermination( + TERMINATION_TIMEOUT_SECONDS, + TimeUnit.SECONDS + ); + if (!terminated) { + log.error("Evelyn status-index scheduler did not terminate"); + } + } catch (InterruptedException exception) { + schedulerToStop.shutdownNow(); + Thread.currentThread().interrupt(); + } finally { + clearStatusIndexHistory(); + synchronized (this) { + if (terminated || schedulerToStop.isTerminated()) { + statusIndexScheduler = null; + stoppingStatusIndexSampling = false; + } + } + } } + @Override + public @NotNull List getStatusIndexHistory() { + synchronized (statusIndexHistory) { + return List.copyOf(statusIndexHistory); + } + } + + private void clearStatusIndexHistory() { + synchronized (statusIndexHistory) { + statusIndexHistory.clear(); + } + } + + private void sampleStatusIndexSafely() { + try { + Instant samplingInstant = clock.instant(); + PriceObservation observation = tickerService.getLatestPrice(eveUsdtTradingPair); + BigDecimal actualPrice = observation.price().price(); + BigDecimal evelynPriceIndex = EvelynPriceIndexCalculator.calculateIndex( + actualPrice, + samplingInstant + ); + EvelynStatusIndexPoint point = new EvelynStatusIndexPoint( + samplingInstant, + evelynPriceIndex, + BigDecimal.ZERO, + BigDecimal.ZERO, + BigDecimal.ZERO, + BigDecimal.ZERO, + BigDecimal.ZERO + ); + if (!isStatusIndexSamplingActive()) { + return; + } + synchronized (statusIndexHistory) { + statusIndexHistory.add(point); + } + } catch (RuntimeException exception) { + log.warn( + "Could not sample Evelyn Price Index: tradingPair={}", + eveUsdtTradingPair, + exception + ); + } + } + + private synchronized boolean isStatusIndexSamplingActive() { + return statusIndexScheduler != null && !stoppingStatusIndexSampling; + } + + private static final Logger log = LoggerFactory.getLogger(EvelynImpl.class); + private static final long SAMPLE_DELAY_MINUTES = 1; + private static final long TERMINATION_TIMEOUT_SECONDS = 10; + + private final TickerService tickerService; + private final TradingPair eveUsdtTradingPair; + private final Clock clock; + private final long sampleDelay; + private final TimeUnit sampleDelayUnit; + private final List statusIndexHistory = new ArrayList<>(); + + private ScheduledExecutorService statusIndexScheduler; + private boolean stoppingStatusIndexSampling; + /* private SPLTokenHolding getSPLHolding(ΩSolanaAddressΩ ownerAddress, ΩSPLMintAddressΩ splMintAddress) throws Exception { Map<ΩSPLMintAddressΩ, SPLTokenHolding> holdings = new HashMap<>(); diff --git a/src/main/tjava/com/fanitas/evelyn/core/impl/ref/EvelynPriceIndexCalculator.tjava b/src/main/tjava/com/fanitas/evelyn/core/impl/ref/EvelynPriceIndexCalculator.tjava new file mode 100644 index 0000000..670c6b5 --- /dev/null +++ b/src/main/tjava/com/fanitas/evelyn/core/impl/ref/EvelynPriceIndexCalculator.tjava @@ -0,0 +1,63 @@ +package com.fanitas.evelyn.core.impl.ref; + +import org.jetbrains.annotations.NotNull; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.time.Duration; +import java.time.Instant; +import java.util.Objects; + +final class EvelynPriceIndexCalculator { + static @NotNull BigDecimal calculateExpectedPrice(@NotNull Instant samplingInstant) { + Objects.requireNonNull(samplingInstant, "samplingInstant"); + if (samplingInstant.isBefore(EXPECTED_PRICE_START)) { + throw new IllegalArgumentException( + "samplingInstant is before the expected-price start: " + samplingInstant + ); + } + + Duration elapsed = Duration.between(EXPECTED_PRICE_START, samplingInstant); + BigDecimal elapsedSeconds = BigDecimal.valueOf(elapsed.getSeconds()).add( + BigDecimal.valueOf(elapsed.getNano(), 9), + MATH_CONTEXT + ); + BigDecimal elapsedYears = elapsedSeconds.divide(SECONDS_PER_YEAR, MATH_CONTEXT); + double growthFactor = Math.pow( + ANNUAL_GROWTH_FACTOR.doubleValue(), + elapsedYears.doubleValue() + ); + + BigDecimal result = START_PRICE.multiply(BigDecimal.valueOf(growthFactor), MATH_CONTEXT); + + return result; + } + + static @NotNull BigDecimal calculateIndex( + @NotNull BigDecimal actualPrice, + @NotNull Instant samplingInstant + ) { + Objects.requireNonNull(actualPrice, "actualPrice"); + if (actualPrice.signum() < 0) { + throw new IllegalArgumentException("actualPrice must not be negative"); + } + + BigDecimal expectedPrice = calculateExpectedPrice(samplingInstant); + BigDecimal result = actualPrice.divide(expectedPrice, MATH_CONTEXT) + .subtract(BigDecimal.ONE, MATH_CONTEXT) + .multiply(INDEX_SCALE, MATH_CONTEXT); + + return result; + } + + static final Instant EXPECTED_PRICE_START = Instant.parse("2026-07-31T22:00:00Z"); + static final MathContext MATH_CONTEXT = MathContext.DECIMAL128; + + private static final BigDecimal START_PRICE = new BigDecimal("15.00"); + private static final BigDecimal ANNUAL_GROWTH_FACTOR = new BigDecimal("1.20"); + private static final BigDecimal SECONDS_PER_YEAR = new BigDecimal("31536000"); + private static final BigDecimal INDEX_SCALE = BigDecimal.TEN; + + private EvelynPriceIndexCalculator() { + } +} diff --git a/src/main/tjava/com/r35157/evelyn/emc/impl/ref/EvelynMissionControlImpl.tjava b/src/main/tjava/com/r35157/evelyn/emc/impl/ref/EvelynMissionControlImpl.tjava index 4fc5dc1..f10a97b 100644 --- a/src/main/tjava/com/r35157/evelyn/emc/impl/ref/EvelynMissionControlImpl.tjava +++ b/src/main/tjava/com/r35157/evelyn/emc/impl/ref/EvelynMissionControlImpl.tjava @@ -4,6 +4,9 @@ import com.fanitas.evelyn.core.Evelyn; import com.fanitas.evelyn.core.EvelynStatusIndexPoint; import com.r35157.evelyn.emc.EvelynMissionControl; import com.r35157.libs.javafx.JavaFxRuntime; +import javafx.animation.KeyFrame; +import javafx.animation.Timeline; +import javafx.application.Platform; import javafx.scene.Scene; import javafx.scene.chart.LineChart; import javafx.scene.chart.NumberAxis; @@ -13,6 +16,7 @@ import javafx.scene.control.TabPane; import javafx.scene.layout.StackPane; import javafx.stage.Stage; import javafx.util.StringConverter; +import javafx.util.Duration; import java.math.BigDecimal; import java.time.Instant; @@ -27,6 +31,7 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl { public EvelynMissionControlImpl(Evelyn evelynProd, Evelyn evelynTest) { this.evelynProd = Objects.requireNonNull(evelynProd); this.evelynTest = Objects.requireNonNull(evelynTest); + refreshTimeline.setCycleCount(Timeline.INDEFINITE); } @Override @@ -35,15 +40,26 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl { } private void showWindow() { - Stage window = new Stage(); - window.setTitle("Evelyn Mission Control"); + if (window != null && window.isShowing()) { + window.toFront(); + return; + } + + if (window == null) { + window = new Stage(); + window.setTitle("Evelyn Mission Control"); + window.setOnHidden(event -> refreshTimeline.stop()); + } window.setScene(new Scene(createRootTabs(), 600, 400)); + + refreshCharts(); window.show(); + refreshTimeline.playFromStart(); } - private TabPane createMissionControlTabs(String color, List statusIndexHistory) { + private TabPane createMissionControlTabs(String color, StatusIndexChart statusIndexChart) { TabPane tabs = new TabPane( - createOverviewTab(color, statusIndexHistory), + createOverviewTab(color, statusIndexChart), createMissionControlTab("Portfolio", color), createMissionControlTab("Perps", color), createMissionControlTab("Spot", color), @@ -57,8 +73,8 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl { return tabs; } - private Tab createOverviewTab(String color, List statusIndexHistory) { - StackPane content = new StackPane(createStatusIndexChart(statusIndexHistory)); + private Tab createOverviewTab(String color, StatusIndexChart statusIndexChart) { + StackPane content = new StackPane(statusIndexChart.chart()); content.setStyle("-fx-background-color: " + color + ";"); Tab tab = new Tab("Overview", content); @@ -66,24 +82,13 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl { return tab; } - private LineChart createStatusIndexChart(List history) { - long min = history.stream() - .mapToLong(point -> point.timestamp().toEpochMilli()) - .min() - .orElse(0L); - - long max = history.stream() - .mapToLong(point -> point.timestamp().toEpochMilli()) - .max() - .orElse(0L); - - double padding = 0; - NumberAxis xAxis = new NumberAxis(min - padding, max + padding, (max - min) / 4.0); + static StatusIndexChart createStatusIndexChart() { + NumberAxis xAxis = new NumberAxis(0, 1, 1); xAxis.setLabel("Time"); xAxis.setTickLabelFormatter(TIMESTAMP_FORMATTER); xAxis.setForceZeroInRange(false); - double yBound = calculateYBound(history); + double yBound = EMPTY_Y_BOUND; NumberAxis yAxis = new NumberAxis(-yBound, yBound, yBound / 5.0); yAxis.setLabel("Index value"); yAxis.setForceZeroInRange(true); @@ -92,16 +97,21 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl { chart.setTitle("Evelyn Status Index History"); chart.setCreateSymbols(false); chart.setAnimated(false); - chart.getData().add(createSeries("Evelyn Price Index", history, EvelynStatusIndexPoint::evelynPriceIndex)); - chart.getData().add(createSeries("EVE_SYRUP Pool Depth Index", history, EvelynStatusIndexPoint::eveSyrupPoolDepthIndex)); - chart.getData().add(createSeries("EVE_SYRUP Pool Balance Index", history, EvelynStatusIndexPoint::eveSyrupPoolBalanceIndex)); - chart.getData().add(createSeries("AAZDKK_USDT Pool Balance Index", history, EvelynStatusIndexPoint::aazdkkUsdtPoolBalanceIndex)); - chart.getData().add(createSeries("AAZDKK_USDT Pool Price Index", history, EvelynStatusIndexPoint::aazdkkUsdtPoolPriceIndex)); - chart.getData().add(createSeries("AAZDKK_USDT Pool Depth Index", history, EvelynStatusIndexPoint::aazdkkUsdtPoolDepthIndex)); - return chart; + XYChart.Series priceIndexSeries = createSeries( + "Evelyn Price Index", + List.of(), + EvelynStatusIndexPoint::evelynPriceIndex + ); + chart.getData().add(priceIndexSeries); + //chart.getData().add(createSeries("EVE_SYRUP Pool Depth Index", history, EvelynStatusIndexPoint::eveSyrupPoolDepthIndex)); + //chart.getData().add(createSeries("EVE_SYRUP Pool Balance Index", history, EvelynStatusIndexPoint::eveSyrupPoolBalanceIndex)); + //chart.getData().add(createSeries("AAZDKK_USDT Pool Balance Index", history, EvelynStatusIndexPoint::aazdkkUsdtPoolBalanceIndex)); + //chart.getData().add(createSeries("AAZDKK_USDT Pool Price Index", history, EvelynStatusIndexPoint::aazdkkUsdtPoolPriceIndex)); + //chart.getData().add(createSeries("AAZDKK_USDT Pool Depth Index", history, EvelynStatusIndexPoint::aazdkkUsdtPoolDepthIndex)); + return new StatusIndexChart(chart, xAxis, yAxis, priceIndexSeries); } - private XYChart.Series createSeries( + private static XYChart.Series createSeries( String name, List history, Function valueExtractor @@ -117,25 +127,88 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl { return series; } - private double calculateYBound(List history) { + static double calculateYBound(List history) { BigDecimal maxAbsoluteValue = BigDecimal.ZERO; for (EvelynStatusIndexPoint point : history) { maxAbsoluteValue = max(maxAbsoluteValue, point.evelynPriceIndex()); - maxAbsoluteValue = max(maxAbsoluteValue, point.eveSyrupPoolDepthIndex()); - maxAbsoluteValue = max(maxAbsoluteValue, point.eveSyrupPoolBalanceIndex()); - maxAbsoluteValue = max(maxAbsoluteValue, point.aazdkkUsdtPoolBalanceIndex()); - maxAbsoluteValue = max(maxAbsoluteValue, point.aazdkkUsdtPoolPriceIndex()); - maxAbsoluteValue = max(maxAbsoluteValue, point.aazdkkUsdtPoolDepthIndex()); + //maxAbsoluteValue = max(maxAbsoluteValue, point.eveSyrupPoolDepthIndex()); + //maxAbsoluteValue = max(maxAbsoluteValue, point.eveSyrupPoolBalanceIndex()); + //maxAbsoluteValue = max(maxAbsoluteValue, point.aazdkkUsdtPoolBalanceIndex()); + //maxAbsoluteValue = max(maxAbsoluteValue, point.aazdkkUsdtPoolPriceIndex()); + //maxAbsoluteValue = max(maxAbsoluteValue, point.aazdkkUsdtPoolDepthIndex()); } double bound = maxAbsoluteValue.doubleValue(); return bound == 0.0 ? EMPTY_Y_BOUND : bound; } - private BigDecimal max(BigDecimal currentMaximum, BigDecimal candidate) { + private static BigDecimal max(BigDecimal currentMaximum, BigDecimal candidate) { return currentMaximum.max(candidate.abs()); } + static void refreshChart( + StatusIndexChart statusIndexChart, + List history + ) { + if (!Platform.isFxApplicationThread()) { + throw new IllegalStateException("Status-index charts must be updated on the JavaFX Application Thread"); + } + + int commonPointCount = 0; + int maximumCommonPointCount = Math.min( + history.size(), + statusIndexChart.renderedHistory.size() + ); + while (commonPointCount < maximumCommonPointCount + && history.get(commonPointCount).equals( + statusIndexChart.renderedHistory.get(commonPointCount) + )) { + commonPointCount++; + } + if (commonPointCount < statusIndexChart.renderedHistory.size()) { + statusIndexChart.priceIndexSeries().getData().clear(); + statusIndexChart.renderedHistory = List.of(); + commonPointCount = 0; + } + for (int index = commonPointCount; index < history.size(); index++) { + EvelynStatusIndexPoint point = history.get(index); + statusIndexChart.priceIndexSeries().getData().add(new XYChart.Data<>( + point.timestamp().toEpochMilli(), + point.evelynPriceIndex() + )); + } + statusIndexChart.renderedHistory = List.copyOf(history); + updateAxisBounds(statusIndexChart, history); + } + + private static void updateAxisBounds( + StatusIndexChart statusIndexChart, + List history + ) { + long minimumTimestamp; + long maximumTimestamp; + if (history.isEmpty()) { + minimumTimestamp = 0; + maximumTimestamp = 1; + } else { + minimumTimestamp = history.getFirst().timestamp().toEpochMilli(); + maximumTimestamp = history.getLast().timestamp().toEpochMilli(); + if (minimumTimestamp == maximumTimestamp) { + minimumTimestamp -= SINGLE_POINT_X_PADDING_MILLIS; + maximumTimestamp += SINGLE_POINT_X_PADDING_MILLIS; + } + } + double xTickUnit = Math.max(1, (maximumTimestamp - minimumTimestamp) / 4.0); + statusIndexChart.xAxis().setLowerBound(minimumTimestamp); + statusIndexChart.xAxis().setUpperBound(maximumTimestamp); + statusIndexChart.xAxis().setTickUnit(xTickUnit); + + double yBound = calculateYBound(history); + statusIndexChart.yAxis().setLowerBound(-yBound); + statusIndexChart.yAxis().setUpperBound(yBound); + statusIndexChart.yAxis().setTickUnit(yBound / 5.0); + } + private Tab createMissionControlTab(String title, String color) { StackPane content = new StackPane(); content.setStyle("-fx-background-color: " + color + ";"); @@ -147,18 +220,18 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl { } private TabPane createRootTabs() { - List statusIndexHistoryProd = List.copyOf(evelynProd.getStatusIndexHistory()); - List statusIndexHistoryTest = List.copyOf(evelynTest.getStatusIndexHistory()); + productionChart = createStatusIndexChart(); + testChart = createStatusIndexChart(); Tab productionTab = new Tab( "Production", - createMissionControlTabs(PRODUCTION_COLOR, statusIndexHistoryProd) + createMissionControlTabs(PRODUCTION_COLOR, productionChart) ); productionTab.setStyle("-fx-background-color: " + PRODUCTION_COLOR + ";"); Tab testTab = new Tab( "Test", - createMissionControlTabs(TEST_COLOR, statusIndexHistoryTest) + createMissionControlTabs(TEST_COLOR, testChart) ); testTab.setStyle("-fx-background-color: " + TEST_COLOR + ";"); @@ -168,9 +241,52 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl { return rootTabs; } + private void refreshCharts() { + refreshChart(productionChart, evelynProd.getStatusIndexHistory()); + refreshChart(testChart, evelynTest.getStatusIndexHistory()); + } + + static final class StatusIndexChart { + private StatusIndexChart( + LineChart chart, + NumberAxis xAxis, + NumberAxis yAxis, + XYChart.Series priceIndexSeries + ) { + this.chart = chart; + this.xAxis = xAxis; + this.yAxis = yAxis; + this.priceIndexSeries = priceIndexSeries; + } + + LineChart chart() { + return chart; + } + + NumberAxis xAxis() { + return xAxis; + } + + NumberAxis yAxis() { + return yAxis; + } + + XYChart.Series priceIndexSeries() { + return priceIndexSeries; + } + + private final LineChart chart; + private final NumberAxis xAxis; + private final NumberAxis yAxis; + private final XYChart.Series priceIndexSeries; + private List renderedHistory = List.of(); + } + private static final String PRODUCTION_COLOR = "#ffd6d6"; private static final String TEST_COLOR = "#d8f3dc"; private static final double EMPTY_Y_BOUND = 1.0; + private static final double CHART_REFRESH_SECONDS = 1.0; + private static final long SINGLE_POINT_X_PADDING_MILLIS = 30_000; private static final DateTimeFormatter TIMESTAMP_TICK_FORMATTER = DateTimeFormatter.ofPattern("HH:mm dd/MM/yy") .withZone(ZoneId.of("Europe/Copenhagen")); @@ -188,4 +304,11 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl { private final Evelyn evelynProd; private final Evelyn evelynTest; + private final Timeline refreshTimeline = new Timeline(new KeyFrame( + Duration.seconds(CHART_REFRESH_SECONDS), + event -> refreshCharts() + )); + private Stage window; + private StatusIndexChart productionChart; + private StatusIndexChart testChart; } diff --git a/src/main/tjava/com/r35157/nenjim/hubd/impl/ref/NenjimHubImpl.tjava b/src/main/tjava/com/r35157/nenjim/hubd/impl/ref/NenjimHubImpl.tjava index 3fe29d9..93ef5f9 100644 --- a/src/main/tjava/com/r35157/nenjim/hubd/impl/ref/NenjimHubImpl.tjava +++ b/src/main/tjava/com/r35157/nenjim/hubd/impl/ref/NenjimHubImpl.tjava @@ -3,6 +3,7 @@ package com.r35157.nenjim.hubd.impl.ref; import com.fanitas.evelyn.core.Evelyn; import com.fanitas.evelyn.core.impl.ref.EvelynImpl; import com.r35157.assetaz.services.ticker.plugins.pricesource.PriceSource; +import com.r35157.assetaz.services.ticker.TickerService; import com.r35157.assetaz.services.ticker.plugins.pricesource.impl.hardcoded.HardcodedPriceSource; import com.r35157.assetaz.services.ticker.plugins.pricesource.impl.raydiumpool.RaydiumPoolPriceSource; import com.r35157.assetaz.services.ticker.impl.ref.TickerServiceImpl; @@ -78,12 +79,15 @@ public class NenjimHubImpl implements NenjimHub { //PriceSource hardcodedPriceSource = new HardcodedPriceSource(cis); //PriceSource raydiumPoolPriceSource = createEVEUSDTPriceSource(cis, raydium); - //startAssetAZTickerService(hardcodedPriceSource, raydiumPoolPriceSource); + //TickerService tickerService = startAssetAZTickerService(raydiumPoolPriceSource); //startJupiterPerpsAlarm(cis); - //Evelyn evelynProd = new EvelynImpl(); - //Evelyn evelynTest = new EvelynImpl(); + //TradingPair eveUsdt = createEVEUSDTTradingPair(cis); + //Evelyn evelynProd = new EvelynImpl(tickerService, eveUsdt); + //Evelyn evelynTest = new EvelynImpl(tickerService, eveUsdt); + //evelynProd.start(); + //evelynTest.start(); //startEvelynMissionControl(evelynProd, evelynTest); //startNenjimComposer(); //startNenjimProcessManager(); @@ -110,16 +114,20 @@ public class NenjimHubImpl implements NenjimHub { } private PriceSource createEVEUSDTPriceSource(CurrencyIdentityService cis, Raydium raydium) { - TradingPair eveUsdt = new TradingPair(cis.resolve(EVE_ID), cis.resolve(USDT_ID)); + TradingPair eveUsdt = createEVEUSDTTradingPair(cis); PriceSource priceSource = new RaydiumPoolPriceSource(raydium, EVE_USDT_RAYDIUM_POOL_ID, eveUsdt); return priceSource; } - private void startAssetAZTickerService(PriceSource... priceSources) { - TickerServiceImpl tickerService = new TickerServiceImpl(priceSources); + private TradingPair createEVEUSDTTradingPair(CurrencyIdentityService cis) { + return new TradingPair(cis.resolve(EVE_ID), cis.resolve(USDT_ID)); + } + private TickerService startAssetAZTickerService(PriceSource... priceSources) { + TickerService tickerService = new TickerServiceImpl(priceSources); tickerService.start(); + return tickerService; } @Override