43: Prevent Position Decrease from reducing a position below a configured size reserve

This commit is contained in:
2026-08-05 10:12:39 +02:00
parent 3ba92bae4b
commit d4ced47012
4 changed files with 76 additions and 3 deletions
@@ -1,8 +1,8 @@
# Wallet and signer used for position-decrease transactions # Wallet and signer used for position-decrease transactions
################################################################ ################################################################
WALLET_ID {{JUPITER_PERPS_WALLET}} WALLET_ID {{JUPITER_PERPS_WALLET}}
SIGNER_KEY_NAME <SIGNER> SIGNER_KEY_NAME <SIGNER>
RESERVE_POSITION_SIZE_USD_LIMIT 10
# Id Asset Direction ReceiveToken SizeDeltaUSD MaxSlippageBPS # Id Asset Direction ReceiveToken SizeDeltaUSD MaxSlippageBPS
######################################################################## ########################################################################
@@ -9,6 +9,7 @@ import com.r35157.libs.solana.SolanaSignedTransaction;
import com.r35157.libs.solana.SolanaUnsignedTransaction; import com.r35157.libs.solana.SolanaUnsignedTransaction;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Objects; import java.util.Objects;
@@ -68,6 +69,10 @@ public final class JupiterPerpsPositionDecreaseAlarmAction
JupiterPerpsPosition position = JupiterPerpsPosition position =
findOpenPosition(positionDecrease); findOpenPosition(positionDecrease);
if (!isPositionSizeOk(position, positionDecrease)) {
return;
}
SolanaUnsignedTransaction utrans = SolanaUnsignedTransaction utrans =
jupiter.buildPositionDecreaseTransaction( jupiter.buildPositionDecreaseTransaction(
position.positionAccount(), position.positionAccount(),
@@ -76,8 +81,10 @@ public final class JupiterPerpsPositionDecreaseAlarmAction
positionDecrease.sizeUsdDelta(), positionDecrease.sizeUsdDelta(),
positionDecrease.maxSlippageBps() positionDecrease.maxSlippageBps()
); );
SolanaSignedTransaction strans = SolanaSignedTransaction strans =
wallet.signTransaction(utrans); wallet.signTransaction(utrans);
ΩSolanaTransactionSignatureΩ signature = ΩSolanaTransactionSignatureΩ signature =
jupiter.executePositionDecreaseTransaction(strans); jupiter.executePositionDecreaseTransaction(strans);
@@ -135,6 +142,39 @@ public final class JupiterPerpsPositionDecreaseAlarmAction
return matchingPosition; return matchingPosition;
} }
private boolean isPositionSizeOk(
JupiterPerpsPosition position,
JupiterPerpsPositionDecreaseAlarmActionConfiguration
.ResolvedPositionDecrease positionDecrease
) {
ΩUSDCAmountΩ currentPositionSize = position.positionSize();
ΩUSDCAmountΩ decreaseSizeUsd = positionDecrease.sizeUsdDelta();
ΩUSDCAmountΩ reservePositionSizeUsdLimit =
configuration.reservePositionSizeUsdLimit();
ΩUSDCAmountΩ remainingPositionSize =
currentPositionSize.subtract(decreaseSizeUsd);
if (remainingPositionSize.compareTo(
reservePositionSizeUsdLimit
) < 0) {
System.err.println(
"Position Decrease of "
+ decreaseSizeUsd
+ " USD skipped: Position size is "
+ currentPositionSize
+ " USD and would become "
+ remainingPositionSize
+ " USD, which is below the "
+ reservePositionSizeUsdLimit
+ " USD reserve."
);
return false;
}
return true;
}
private final JupiterPerpsPositionDecreaseAlarmActionConfiguration private final JupiterPerpsPositionDecreaseAlarmActionConfiguration
configuration; configuration;
private final SolanaWallet wallet; private final SolanaWallet wallet;
@@ -10,11 +10,21 @@ import java.util.Objects;
public record JupiterPerpsPositionDecreaseAlarmActionConfiguration( public record JupiterPerpsPositionDecreaseAlarmActionConfiguration(
ΩSolanaWalletIdΩ walletId, ΩSolanaWalletIdΩ walletId,
String signerKeyName, String signerKeyName,
ΩUSDCAmountΩ reservePositionSizeUsdLimit,
Map<Integer, PositionDecrease> positionDecreases Map<Integer, PositionDecrease> positionDecreases
) { ) {
public JupiterPerpsPositionDecreaseAlarmActionConfiguration { public JupiterPerpsPositionDecreaseAlarmActionConfiguration {
Objects.requireNonNull(walletId, "walletId"); Objects.requireNonNull(walletId, "walletId");
Objects.requireNonNull(signerKeyName, "signerKeyName"); Objects.requireNonNull(signerKeyName, "signerKeyName");
Objects.requireNonNull(reservePositionSizeUsdLimit, "reservePositionSizeUsdLimit");
if (reservePositionSizeUsdLimit.signum() < 0) {
throw new IllegalArgumentException(
"Position size USD reserve limit cannot be negative: "
+ reservePositionSizeUsdLimit
);
}
positionDecreases = Map.copyOf(positionDecreases); positionDecreases = Map.copyOf(positionDecreases);
if (walletId.isBlank()) { if (walletId.isBlank()) {
@@ -1,6 +1,7 @@
package com.r35157.jupiterperpsalarm.impl.ref; package com.r35157.jupiterperpsalarm.impl.ref;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -20,6 +21,7 @@ public final class JupiterPerpsPositionDecreaseAlarmActionConfigurationParser {
positionDecreases = new LinkedHashMap<>(); positionDecreases = new LinkedHashMap<>();
ΩSolanaWalletIdΩ walletId = null; ΩSolanaWalletIdΩ walletId = null;
String signerKeyName = null; String signerKeyName = null;
ΩUSDCAmountΩ reservePositionSizeUsdLimit = null;
for (int lineNumber = 1; lineNumber <= lines.size(); lineNumber++) { for (int lineNumber = 1; lineNumber <= lines.size(); lineNumber++) {
String line = removeComment(lines.get(lineNumber - 1)).trim(); String line = removeComment(lines.get(lineNumber - 1)).trim();
@@ -51,6 +53,20 @@ public final class JupiterPerpsPositionDecreaseAlarmActionConfigurationParser {
); );
} }
signerKeyName = variableResolver.resolve(columns[1]); signerKeyName = variableResolver.resolve(columns[1]);
} else if (firstColumn.equals("RESERVE_POSITION_SIZE_USD_LIMIT")) {
requireColumnCount(
columns,
2,
"RESERVE_POSITION_SIZE_USD_LIMIT value"
);
if (reservePositionSizeUsdLimit != null) {
throw new IllegalArgumentException(
"Duplicate RESERVE_POSITION_SIZE_USD_LIMIT"
);
}
reservePositionSizeUsdLimit = new ΩUSDCAmountΩ(
variableResolver.resolve(columns[1])
);
} else { } else {
parsePositionDecrease( parsePositionDecrease(
columns, columns,
@@ -78,9 +94,16 @@ public final class JupiterPerpsPositionDecreaseAlarmActionConfigurationParser {
); );
} }
if (reservePositionSizeUsdLimit == null) {
throw new IllegalArgumentException(
"Missing RESERVE_POSITION_SIZE_USD_LIMIT in " + path
);
}
return new JupiterPerpsPositionDecreaseAlarmActionConfiguration( return new JupiterPerpsPositionDecreaseAlarmActionConfiguration(
walletId, walletId,
signerKeyName, signerKeyName,
reservePositionSizeUsdLimit,
positionDecreases positionDecreases
); );
} }