59: Show Evelyn status indices in Mission Control Overview
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user