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
@@ -2,7 +2,7 @@
################################################################
WALLET_ID {{JUPITER_PERPS_WALLET}}
SIGNER_KEY_NAME <SIGNER>
RESERVE_POSITION_SIZE_USD_LIMIT 10
# Id Asset Direction ReceiveToken SizeDeltaUSD MaxSlippageBPS
########################################################################
@@ -9,6 +9,7 @@ import com.r35157.libs.solana.SolanaSignedTransaction;
import com.r35157.libs.solana.SolanaUnsignedTransaction;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Path;
import java.util.Objects;
@@ -68,6 +69,10 @@ public final class JupiterPerpsPositionDecreaseAlarmAction
JupiterPerpsPosition position =
findOpenPosition(positionDecrease);
if (!isPositionSizeOk(position, positionDecrease)) {
return;
}
SolanaUnsignedTransaction utrans =
jupiter.buildPositionDecreaseTransaction(
position.positionAccount(),
@@ -76,8 +81,10 @@ public final class JupiterPerpsPositionDecreaseAlarmAction
positionDecrease.sizeUsdDelta(),
positionDecrease.maxSlippageBps()
);
SolanaSignedTransaction strans =
wallet.signTransaction(utrans);
ΩSolanaTransactionSignatureΩ signature =
jupiter.executePositionDecreaseTransaction(strans);
@@ -135,6 +142,39 @@ public final class JupiterPerpsPositionDecreaseAlarmAction
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
configuration;
private final SolanaWallet wallet;
@@ -10,11 +10,21 @@ import java.util.Objects;
public record JupiterPerpsPositionDecreaseAlarmActionConfiguration(
ΩSolanaWalletIdΩ walletId,
String signerKeyName,
ΩUSDCAmountΩ reservePositionSizeUsdLimit,
Map<Integer, PositionDecrease> positionDecreases
) {
public JupiterPerpsPositionDecreaseAlarmActionConfiguration {
Objects.requireNonNull(walletId, "walletId");
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);
if (walletId.isBlank()) {
@@ -1,6 +1,7 @@
package com.r35157.jupiterperpsalarm.impl.ref;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap;
@@ -20,6 +21,7 @@ public final class JupiterPerpsPositionDecreaseAlarmActionConfigurationParser {
positionDecreases = new LinkedHashMap<>();
ΩSolanaWalletIdΩ walletId = null;
String signerKeyName = null;
ΩUSDCAmountΩ reservePositionSizeUsdLimit = null;
for (int lineNumber = 1; lineNumber <= lines.size(); lineNumber++) {
String line = removeComment(lines.get(lineNumber - 1)).trim();
@@ -51,6 +53,20 @@ public final class JupiterPerpsPositionDecreaseAlarmActionConfigurationParser {
);
}
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 {
parsePositionDecrease(
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(
walletId,
signerKeyName,
reservePositionSizeUsdLimit,
positionDecreases
);
}