97: Configure separate TEST and PROD instances of EvelynPerpsService

This commit is contained in:
2026-10-22 11:32:49 +02:00
parent 7aab307381
commit 0ec22d5215
16 changed files with 308 additions and 57 deletions
@@ -41,12 +41,14 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl, Nen
public EvelynMissionControlImpl(
@NotNull EvelynStatusService evelynStatusServiceProd,
@NotNull EvelynStatusService evelynStatusServiceTest,
@NotNull EvelynPerpsService evelynPerpsService,
@NotNull EvelynPerpsService evelynPerpsServiceProd,
@NotNull EvelynPerpsService evelynPerpsServiceTest,
@NotNull JavaFxRuntimeService javaFxRuntimeService
) {
this.evelynStatusProd = Objects.requireNonNull(evelynStatusServiceProd, "evelynStatusServiceProd");
this.evelynStatusTest = Objects.requireNonNull(evelynStatusServiceTest, "evelynStatusServiceTest");
this.evelynPerpsService = Objects.requireNonNull(evelynPerpsService, "evelynPerpsService");
this.evelynPerpsServiceProd = Objects.requireNonNull(evelynPerpsServiceProd, "evelynPerpsServiceProd");
this.evelynPerpsServiceTest = Objects.requireNonNull(evelynPerpsServiceTest, "evelynPerpsServiceTest");
this.javaFxRuntimeService = Objects.requireNonNull(
javaFxRuntimeService,
"javaFxRuntimeService");
@@ -288,20 +290,28 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl, Nen
});
perpsPolling = session;
session.scheduleAtFixedRate(() -> {
try {
PerpsSnapshot snapshot = evelynPerpsService.getPositionsSnapshot();
javaFxRuntimeService.runLater(() -> {
if (perpsPolling == session && window.isShowing()) {
productionPerps.update(snapshot);
testPerps.update(snapshot);
}
});
} catch (RuntimeException exception) {
log.warn("Unable to refresh Perps positions", exception);
}
pollPerps(session, evelynPerpsServiceProd, productionPerps, "PROD");
pollPerps(session, evelynPerpsServiceTest, testPerps, "TEST");
}, 0, PERPS_REFRESH_SECONDS, TimeUnit.SECONDS);
}
private void pollPerps(ScheduledExecutorService session, EvelynPerpsService service,
PerpsTab tab, String environment) {
if (perpsPolling != session) {
return;
}
try {
PerpsSnapshot snapshot = service.getPositionsSnapshot();
javaFxRuntimeService.runLater(() -> {
if (perpsPolling == session && window.isShowing()) {
tab.update(snapshot);
}
});
} catch (RuntimeException exception) {
log.warn("Unable to refresh {} Perps positions", environment, exception);
}
}
private void stopPerpsPolling() {
ScheduledExecutorService session = perpsPolling;
perpsPolling = null;
@@ -382,7 +392,8 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl, Nen
// region Dependencies
private final EvelynStatusService evelynStatusProd;
private final EvelynStatusService evelynStatusTest;
private final EvelynPerpsService evelynPerpsService;
private final EvelynPerpsService evelynPerpsServiceProd;
private final EvelynPerpsService evelynPerpsServiceTest;
private final JavaFxRuntimeService javaFxRuntimeService;
// endregion
@@ -0,0 +1,11 @@
package com.r35157.evelyn.service.perps;
import java.util.Objects;
/** Hub-supplied wallet identity; sample IDs are not real Solana addresses. */
public record PerpsWalletConfiguration(String id, String name) {
public PerpsWalletConfiguration {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(name, "name");
}
}
@@ -2,6 +2,7 @@ package com.r35157.evelyn.service.perps.impl.ref;
import com.r35157.evelyn.service.perps.EvelynPerpsService;
import com.r35157.evelyn.service.perps.PerpsSnapshot;
import com.r35157.evelyn.service.perps.PerpsWalletConfiguration;
import com.r35157.evelyn.service.perps.PerpsSnapshot.Position;
import com.r35157.evelyn.service.perps.PerpsSnapshot.Venue;
import com.r35157.evelyn.service.perps.PerpsSnapshot.Wallet;
@@ -10,31 +11,53 @@ import com.r35157.nenjim.process.NenjimProcess;
import org.jetbrains.annotations.NotNull;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
/** Owns the fixed examples only. No exchange clients, position management or update worker. */
public final class EvelynPerpsServiceImpl implements EvelynPerpsService, NenjimProcess {
public EvelynPerpsServiceImpl(@NotNull List<PerpsWalletConfiguration> wallets) {
this.wallets = List.copyOf(wallets);
var ids = new HashSet<String>();
for (PerpsWalletConfiguration wallet : this.wallets) {
if (!ids.add(wallet.id())) {
throw new IllegalArgumentException("Duplicate Perps wallet ID: " + wallet.id());
}
}
}
@Override
public synchronized void start() {
if (snapshot != null) {
throw new IllegalStateException("Perps service has already started");
}
snapshot = new PerpsSnapshot(List.of(
new Wallet("sample-wallet-a", "Sample wallet A", List.of(
new Venue("jupiter", "Jupiter", List.of(
position("sol-long", "SOL-PERP", Direction.LONG, "12500.00"),
position("btc-short", "BTC-PERP", Direction.SHORT, "8250.50"))),
new Venue("phoenix", "Phoenix", List.of(
position("sol-short", "SOL-PERP", Direction.SHORT, "3200.75"),
position("eth-long", "ETH-PERP", Direction.LONG, "5400.00"))))),
new Wallet("sample-wallet-b", "Sample wallet B", List.of(
new Venue("jupiter", "Jupiter", List.of(
position("eth-long", "ETH-PERP", Direction.LONG, "1875.25"),
position("sol-short", "SOL-PERP", Direction.SHORT, "950.00"))),
new Venue("phoenix", "Phoenix", List.of(
position("btc-long", "BTC-PERP", Direction.LONG, "21000.00"),
position("sol-short", "SOL-PERP", Direction.SHORT, "640.50")))))
));
var configuredWallets = new ArrayList<Wallet>();
for (int index = 0; index < wallets.size(); index++) {
PerpsWalletConfiguration wallet = wallets.get(index);
configuredWallets.add(new Wallet(wallet.id(), wallet.name(), sampleVenues(index)));
}
snapshot = new PerpsSnapshot(configuredWallets);
}
/** Alternate the original two sample portfolios in configured wallet order. */
private static List<Venue> sampleVenues(int walletIndex) {
if (walletIndex % 2 == 0) {
return List.of(
new Venue("jupiter", "Jupiter", List.of(
position("sol-long", "SOL-PERP", Direction.LONG, "12500.00"),
position("btc-short", "BTC-PERP", Direction.SHORT, "8250.50"))),
new Venue("phoenix", "Phoenix", List.of(
position("sol-short", "SOL-PERP", Direction.SHORT, "3200.75"),
position("eth-long", "ETH-PERP", Direction.LONG, "5400.00"))));
}
return List.of(
new Venue("jupiter", "Jupiter", List.of(
position("eth-long", "ETH-PERP", Direction.LONG, "1875.25"),
position("sol-short", "SOL-PERP", Direction.SHORT, "950.00"))),
new Venue("phoenix", "Phoenix", List.of(
position("btc-long", "BTC-PERP", Direction.LONG, "21000.00"),
position("sol-short", "SOL-PERP", Direction.SHORT, "640.50"))));
}
@Override
@@ -50,5 +73,6 @@ public final class EvelynPerpsServiceImpl implements EvelynPerpsService, NenjimP
return new Position(id, instrument, direction, new ΩAmountΩ(sizeUsd));
}
private final List<PerpsWalletConfiguration> wallets;
private volatile PerpsSnapshot snapshot;
}
@@ -8,6 +8,7 @@ import com.r35157.evelyn.service.liquidity.impl.ref.EvelynLiquidityServiceImpl;
import com.r35157.evelyn.service.status.EvelynStatusService;
import com.r35157.evelyn.service.status.impl.ref.EvelynStatusServiceImpl;
import com.r35157.evelyn.service.perps.EvelynPerpsService;
import com.r35157.evelyn.service.perps.PerpsWalletConfiguration;
import com.r35157.evelyn.service.perps.impl.ref.EvelynPerpsServiceImpl;
import com.fanitas.evelyn.service.burner.EvelynIOUBurnerService;
import com.fanitas.evelyn.service.burner.impl.ref.EvelynIOUBurnerServiceImpl;
@@ -360,9 +361,15 @@ public final class NenjimHubImpl implements NenjimHub {
solanaBlockChain, cis, liquidityConfiguration);
}
private NenjimProcess constructEvelynPerpsProcess() {
EvelynPerpsServiceImpl process = new EvelynPerpsServiceImpl();
evelynPerpsService = process;
private NenjimProcess constructEvelynPerpsProdProcess() {
EvelynPerpsServiceImpl process = new EvelynPerpsServiceImpl(EVELYN_PERPS_WALLETS);
evelynPerpsServiceProd = process;
return process;
}
private NenjimProcess constructEvelynPerpsTestProcess() {
EvelynPerpsServiceImpl process = new EvelynPerpsServiceImpl(EVELYN_PERPS_WALLETS);
evelynPerpsServiceTest = process;
return process;
}
@@ -383,9 +390,13 @@ public final class NenjimHubImpl implements NenjimHub {
EVELYN_MISSION_CONTROL_ID,
EVELYN_STATUS_TEST_ID),
requireProcessDependency(
evelynPerpsService,
evelynPerpsServiceProd,
EVELYN_MISSION_CONTROL_ID,
EVELYN_PERPS_SERVICE_ID),
EVELYN_PERPS_PROD_ID),
requireProcessDependency(
evelynPerpsServiceTest,
EVELYN_MISSION_CONTROL_ID,
EVELYN_PERPS_TEST_ID),
requireProcessDependency(
javaFxRuntimeService,
EVELYN_MISSION_CONTROL_ID,
@@ -433,7 +444,8 @@ public final class NenjimHubImpl implements NenjimHub {
Map.entry(EVELYN_STATUS_TEST_ID, this::constructEvelynStatusTestProcess),
Map.entry(EVELYN_LIQUIDITY_PROD_ID, this::constructEvelynLiquidityProdProcess),
Map.entry(EVELYN_IOU_BURNER_PROD_ID, this::constructEvelynIOUBurnerProdProcess),
Map.entry(EVELYN_PERPS_SERVICE_ID, this::constructEvelynPerpsProcess),
Map.entry(EVELYN_PERPS_PROD_ID, this::constructEvelynPerpsProdProcess),
Map.entry(EVELYN_PERPS_TEST_ID, this::constructEvelynPerpsTestProcess),
Map.entry(JAVAFX_RUNTIME_SERVICE_ID, this::constructJavaFxRuntimeProcess),
Map.entry(EVELYN_MISSION_CONTROL_ID, this::constructMissionControlProcess),
Map.entry(PROCESS_MANAGER_ID, this::constructProcessManagerProcess)
@@ -551,8 +563,13 @@ public final class NenjimHubImpl implements NenjimHub {
new NenjimComponentId("evelyn.liquidity.prod");
private static final NenjimComponentId EVELYN_MISSION_CONTROL_ID =
new NenjimComponentId("evelyn.mission-control.default");
private static final NenjimComponentId EVELYN_PERPS_SERVICE_ID =
new NenjimComponentId("evelyn.perps.default");
private static final NenjimComponentId EVELYN_PERPS_PROD_ID =
new NenjimComponentId("evelyn.perps.prod");
private static final NenjimComponentId EVELYN_PERPS_TEST_ID =
new NenjimComponentId("evelyn.perps.test");
private static final List<PerpsWalletConfiguration> EVELYN_PERPS_WALLETS = List.of(
new PerpsWalletConfiguration("sample-wallet-a", "Sample wallet A"),
new PerpsWalletConfiguration("sample-wallet-b", "Sample wallet B"));
private static final NenjimComponentId JAVAFX_RUNTIME_SERVICE_ID =
new NenjimComponentId("nenjim.javafx-runtime.service");
private static final NenjimComponentId PROCESS_MANAGER_ID =
@@ -591,7 +608,8 @@ public final class NenjimHubImpl implements NenjimHub {
private JupiterPerpsAlarm jupiterPerpsAlarm;
private EvelynStatusService evelynStatusServiceProd;
private EvelynStatusService evelynStatusServiceTest;
private EvelynPerpsService evelynPerpsService;
private EvelynPerpsService evelynPerpsServiceProd;
private EvelynPerpsService evelynPerpsServiceTest;
private JupiterSwapService jupiterSwapService;
private BoundNotificationService discordAssetAZTokenNotificationService;
private EvelynIOUBurnerService evelynIOUBurnerServiceProd;