59: Show Evelyn status indices in Mission Control Overview

This commit is contained in:
2026-08-05 14:09:01 +02:00
parent 493a6291a9
commit fdefba91ad
11 changed files with 306 additions and 13 deletions
@@ -2,8 +2,10 @@ package com.fanitas.evelyn.core;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public interface Evelyn {
void executeService() throws Exception;
@NotNull String getStatusReport();
@NotNull List<EvelynStatusIndexPoint> getStatusIndexHistory();
}
@@ -0,0 +1,25 @@
package com.fanitas.evelyn.core;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.Objects;
public record EvelynStatusIndexPoint(
Instant timestamp,
BigDecimal evelynPriceIndex,
BigDecimal eveSyrupPoolDepthIndex,
BigDecimal eveSyrupPoolBalanceIndex,
BigDecimal aazdkkUsdtPoolBalanceIndex,
BigDecimal aazdkkUsdtPoolPriceIndex,
BigDecimal aazdkkUsdtPoolDepthIndex
) {
public EvelynStatusIndexPoint {
Objects.requireNonNull(timestamp);
Objects.requireNonNull(evelynPriceIndex);
Objects.requireNonNull(eveSyrupPoolDepthIndex);
Objects.requireNonNull(eveSyrupPoolBalanceIndex);
Objects.requireNonNull(aazdkkUsdtPoolBalanceIndex);
Objects.requireNonNull(aazdkkUsdtPoolPriceIndex);
Objects.requireNonNull(aazdkkUsdtPoolDepthIndex);
}
}
@@ -16,6 +16,7 @@ import com.r35157.libs.valuetypes.basic.TradingPair;
import org.jetbrains.annotations.NotNull;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -48,8 +49,34 @@ public class EvelynImpl implements Evelyn {
}
@Override
public @NotNull String getStatusReport() {
return "Hello World!";
public @NotNull List<EvelynStatusIndexPoint> 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")
);
}
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)
);
}
/*
@@ -1,17 +1,32 @@
package com.r35157.evelyn.emc.impl.ref;
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.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public final class EvelynMissionControlImpl/*<T>*/ implements EvelynMissionControl {
import java.math.BigDecimal;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
public EvelynMissionControlImpl(/*T evelyn*/) {
//this.evelyn = Objects.requireNonNull(evelyn);
public final class EvelynMissionControlImpl implements EvelynMissionControl {
public EvelynMissionControlImpl(Evelyn evelynProd, Evelyn evelynTest) {
this.evelynProd = Objects.requireNonNull(evelynProd);
this.evelynTest = Objects.requireNonNull(evelynTest);
}
@Override
@@ -26,9 +41,9 @@ public final class EvelynMissionControlImpl/*<T>*/ implements EvelynMissionContr
window.show();
}
private TabPane createMissionControlTabs(String color) {
private TabPane createMissionControlTabs(String color, List<EvelynStatusIndexPoint> statusIndexHistory) {
TabPane tabs = new TabPane(
createMissionControlTab("Overview", color),
createOverviewTab(color, statusIndexHistory),
createMissionControlTab("Portfolio", color),
createMissionControlTab("Perps", color),
createMissionControlTab("Spot", color),
@@ -42,6 +57,85 @@ public final class EvelynMissionControlImpl/*<T>*/ implements EvelynMissionContr
return tabs;
}
private Tab createOverviewTab(String color, List<EvelynStatusIndexPoint> statusIndexHistory) {
StackPane content = new StackPane(createStatusIndexChart(statusIndexHistory));
content.setStyle("-fx-background-color: " + color + ";");
Tab tab = new Tab("Overview", content);
tab.setStyle("-fx-background-color: " + color + ";");
return tab;
}
private LineChart<Number, Number> createStatusIndexChart(List<EvelynStatusIndexPoint> 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);
xAxis.setLabel("Time");
xAxis.setTickLabelFormatter(TIMESTAMP_FORMATTER);
xAxis.setForceZeroInRange(false);
double yBound = calculateYBound(history);
NumberAxis yAxis = new NumberAxis(-yBound, yBound, yBound / 5.0);
yAxis.setLabel("Index value");
yAxis.setForceZeroInRange(true);
LineChart<Number, Number> chart = new LineChart<>(xAxis, yAxis);
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;
}
private XYChart.Series<Number, Number> createSeries(
String name,
List<EvelynStatusIndexPoint> history,
Function<EvelynStatusIndexPoint, BigDecimal> valueExtractor
) {
XYChart.Series<Number, Number> series = new XYChart.Series<>();
series.setName(name);
for (EvelynStatusIndexPoint point : history) {
series.getData().add(new XYChart.Data<>(
point.timestamp().toEpochMilli(),
valueExtractor.apply(point)
));
}
return series;
}
private double calculateYBound(List<EvelynStatusIndexPoint> 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());
}
double bound = maxAbsoluteValue.doubleValue();
return bound == 0.0 ? EMPTY_Y_BOUND : bound;
}
private BigDecimal max(BigDecimal currentMaximum, BigDecimal candidate) {
return currentMaximum.max(candidate.abs());
}
private Tab createMissionControlTab(String title, String color) {
StackPane content = new StackPane();
content.setStyle("-fx-background-color: " + color + ";");
@@ -53,15 +147,18 @@ public final class EvelynMissionControlImpl/*<T>*/ implements EvelynMissionContr
}
private TabPane createRootTabs() {
List<EvelynStatusIndexPoint> statusIndexHistoryProd = List.copyOf(evelynProd.getStatusIndexHistory());
List<EvelynStatusIndexPoint> statusIndexHistoryTest = List.copyOf(evelynTest.getStatusIndexHistory());
Tab productionTab = new Tab(
"Production",
createMissionControlTabs(PRODUCTION_COLOR)
createMissionControlTabs(PRODUCTION_COLOR, statusIndexHistoryProd)
);
productionTab.setStyle("-fx-background-color: " + PRODUCTION_COLOR + ";");
Tab testTab = new Tab(
"Test",
createMissionControlTabs(TEST_COLOR)
createMissionControlTabs(TEST_COLOR, statusIndexHistoryTest)
);
testTab.setStyle("-fx-background-color: " + TEST_COLOR + ";");
@@ -73,4 +170,22 @@ public final class EvelynMissionControlImpl/*<T>*/ implements EvelynMissionContr
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 DateTimeFormatter TIMESTAMP_TICK_FORMATTER =
DateTimeFormatter.ofPattern("HH:mm dd/MM/yy")
.withZone(ZoneId.of("Europe/Copenhagen"));
private static final StringConverter<Number> TIMESTAMP_FORMATTER = new StringConverter<>() {
@Override
public String toString(Number timestamp) {
return TIMESTAMP_TICK_FORMATTER.format(Instant.ofEpochMilli(timestamp.longValue()));
}
@Override
public Number fromString(String value) {
throw new UnsupportedOperationException("Timestamp axis labels are display-only");
}
};
private final Evelyn evelynProd;
private final Evelyn evelynTest;
}
@@ -1,5 +1,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.evelyn.emc.EvelynMissionControl;
import com.r35157.evelyn.emc.impl.ref.EvelynMissionControlImpl;
import com.r35157.jupiterperpsalarm.impl.ref.JupiterPerpsAlarmImpl;
@@ -57,7 +59,9 @@ public class NenjimHubImpl implements NenjimHub {
private void startAutoRunProcesses() throws Exception {
startJupiterPerpsAlarm(); // TODO: Hardcoded/hacky way to auto-start but good enough for now.
startEvelynMissionControl();
Evelyn evelynProd = new EvelynImpl();
Evelyn evelynTest = new EvelynImpl();
startEvelynMissionControl(evelynProd, evelynTest);
startNenjimComposer();
startNenjimProcessManager();
startNenjimTestTool();
@@ -138,10 +142,10 @@ public class NenjimHubImpl implements NenjimHub {
thread.start();
}
private void startEvelynMissionControl() {
private void startEvelynMissionControl(Evelyn evelynProd, Evelyn evelynTest) {
Thread thread = new Thread(() -> {
try {
EvelynMissionControl emc = new EvelynMissionControlImpl();
EvelynMissionControl emc = new EvelynMissionControlImpl(evelynProd, evelynTest);
emc.start();
} catch (Throwable throwable) {
throwable.printStackTrace();