diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsAlarmImpl.tjava b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsAlarmImpl.tjava index 1b69a02..36ee60a 100644 --- a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsAlarmImpl.tjava +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsAlarmImpl.tjava @@ -57,6 +57,8 @@ public final class JupiterPerpsAlarmImpl { entryPriceVariableRefresher.refresh(); System.out.println("Fetching done."); + entryPriceVariableRefresher.startPeriodicRefresh(); + JupiterPerpsEntryPriceVariableRefreshWatcher entryPriceVariableRefreshWatcher = new JupiterPerpsEntryPriceVariableRefreshWatcher( config.alarmConfiguration().getParent(), @@ -106,7 +108,10 @@ public final class JupiterPerpsAlarmImpl { } Runtime.getRuntime().addShutdownHook(new Thread( - () -> clients.forEach(OracleWebSocketClient::close), + () -> { + entryPriceVariableRefresher.close(); + clients.forEach(OracleWebSocketClient::close); + }, "shutdown" )); diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsEntryPriceVariableRefresher.tjava b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsEntryPriceVariableRefresher.tjava index fbc09a1..86e72b2 100644 --- a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsEntryPriceVariableRefresher.tjava +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsEntryPriceVariableRefresher.tjava @@ -2,13 +2,17 @@ package com.r35157.jupiterperpsalarm.impl.ref; import com.r35157.libs.jupiter.perps.JupiterPerpsPosition; import com.r35157.libs.jupiter.perps.JupiterPerpsService; - import java.io.IOException; +import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; -public final class JupiterPerpsEntryPriceVariableRefresher { +public final class JupiterPerpsEntryPriceVariableRefresher + implements AutoCloseable { public JupiterPerpsEntryPriceVariableRefresher( Map variables, @@ -16,13 +20,46 @@ public final class JupiterPerpsEntryPriceVariableRefresher { ) { this.variables = Objects.requireNonNull(variables, "variables"); this.jupiterPerpsService = Objects.requireNonNull(jupiterPerpsService, "jupiterPerpsService"); + + scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { + Thread thread = new Thread( + runnable, + "jupiter-perps-price-variable-refresher" + ); + + thread.setDaemon(true); + + return thread; + }); } - public void refresh() { - ΩSolanaWalletIdΩ wallet = variables.get("JUPITER_PERPS_WALLET"); + public void startPeriodicRefresh() { + scheduler.scheduleWithFixedDelay( + this::refreshPeriodically, + REFRESH_INTERVAL_SECONDS, + REFRESH_INTERVAL_SECONDS, + TimeUnit.SECONDS + ); + + System.out.println( + "Periodic Jupiter Perps price variable refresh started with interval: " + + REFRESH_INTERVAL_SECONDS + + " seconds" + ); + } + + @Override + public void close() { + scheduler.shutdownNow(); + } + + public synchronized void refresh() { + System.out.println("Executing refresh now..."); + + ΩSolanaWalletIdΩ wallet = variables.get("JUPITER_PERPS_WALLET"); if (wallet == null || wallet.isBlank()) { - System.err.println("Cannot refresh Jupiter Perps entry price variables: JUPITER_PERPS_WALLET is not configured"); + System.err.println("Cannot refresh Jupiter Perps price variables: JUPITER_PERPS_WALLET is not configured"); return; } @@ -35,41 +72,92 @@ public final class JupiterPerpsEntryPriceVariableRefresher { + " open Jupiter Perps positions for wallet: " + wallet ); - removeEntryPriceVariables(); + + Map previousPriceVariables = copyPriceVariables(); + + removePriceVariables(); for (JupiterPerpsPosition position : positions) { - String variableName = createEntryPriceVariableName(position); + String entryPriceVariableName = createEntryPriceVariableName(position); - System.out.println( - "Jupiter Perps position maps to variable " - + variableName - + " = " - + position.entryPrice() + putPriceVariable( + previousPriceVariables, + entryPriceVariableName, + position.entryPrice().toPlainString() ); - variables.put(variableName, position.entryPrice().toPlainString()); + + String liquidationPriceVariableName = createLiquidationPriceVariableName(position); + + putPriceVariable( + previousPriceVariables, + liquidationPriceVariableName, + position.liquidationPrice().toPlainString() + ); + } - } catch (IOException | InterruptedException exception) { + } catch (IOException | + InterruptedException exception) { if (exception instanceof InterruptedException) { Thread.currentThread().interrupt(); } System.err.println( - "Could not refresh Jupiter Perps entry price variables: " - + exception.getMessage() + "Could not refresh Jupiter Perps price variables: " + exception.getMessage() ); } } - private void removeEntryPriceVariables() { - variables.remove("SOL_LONG_ENTRY_PRICE"); - variables.remove("SOL_SHORT_ENTRY_PRICE"); - variables.remove("BTC_LONG_ENTRY_PRICE"); - variables.remove("BTC_SHORT_ENTRY_PRICE"); - variables.remove("ETH_LONG_ENTRY_PRICE"); - variables.remove("ETH_SHORT_ENTRY_PRICE"); + private Map copyPriceVariables() { + Map previousPriceVariables = new HashMap<>(); + + for (String variableName : PRICE_VARIABLE_NAMES) { + String value = variables.get(variableName); + + if (value != null) { + previousPriceVariables.put(variableName, value); + } + } + + return previousPriceVariables; + } + + private void removePriceVariables() { + for (String variableName : PRICE_VARIABLE_NAMES) { + variables.remove(variableName); + } + } + + private void putPriceVariable( + Map previousPriceVariables, + String variableName, + String variableValue + ) { + String previousValue = previousPriceVariables.get(variableName); + + variables.put(variableName, variableValue); + + if (previousValue == null || !previousValue.equals(variableValue)) { + System.out.println( + "{{" + variableName + "}} updated from " + + previousValue + + " --> " + + variableValue + ); + } } private static String createEntryPriceVariableName(JupiterPerpsPosition position) { + return createPriceVariableName(position, "ENTRY_PRICE"); + } + + private static String createLiquidationPriceVariableName(JupiterPerpsPosition position) { + return createPriceVariableName(position, "LIQ_PRICE"); + } + + private static String createPriceVariableName( + JupiterPerpsPosition position, + String suffix + ) { String asset = switch (position.tradedTokenMint()) { case "So11111111111111111111111111111111111111112" -> "SOL"; case "3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh" -> "BTC"; @@ -79,9 +167,45 @@ public final class JupiterPerpsEntryPriceVariableRefresher { ); }; - return asset + "_" + position.direction() + "_ENTRY_PRICE"; + return asset + "_" + position.direction() + "_" + suffix; } + private static final Set PRICE_VARIABLE_NAMES = Set.of( + "SOL_LONG_ENTRY_PRICE", + "SOL_SHORT_ENTRY_PRICE", + + "BTC_LONG_ENTRY_PRICE", + "BTC_SHORT_ENTRY_PRICE", + + "ETH_LONG_ENTRY_PRICE", + "ETH_SHORT_ENTRY_PRICE", + + "SOL_LONG_LIQ_PRICE", + "SOL_SHORT_LIQ_PRICE", + + "BTC_LONG_LIQ_PRICE", + "BTC_SHORT_LIQ_PRICE", + + "ETH_LONG_LIQ_PRICE", + "ETH_SHORT_LIQ_PRICE" + ); + + private void refreshPeriodically() { + try { + refresh(); + } catch (RuntimeException exception) { + System.err.println( + "Unexpected error during periodic Jupiter Perps price variable refresh: " + + exception.getMessage() + ); + + exception.printStackTrace(System.err); + } + } + + private static final ΩsecondsΩ REFRESH_INTERVAL_SECONDS = 60; + + private final ScheduledExecutorService scheduler; private final Map variables; private final JupiterPerpsService jupiterPerpsService; } \ No newline at end of file