More fixes

This commit is contained in:
2026-06-15 11:24:02 +02:00
parent 3b24731630
commit a65ac072e7
7 changed files with 271 additions and 180 deletions
@@ -1,6 +1,7 @@
package com.fanitas.evelyn.core; package com.fanitas.evelyn.core;
import com.r35157.libs.basic.Pair; import com.r35157.libs.basic.Pair;
import com.r35157.libs.valuetypes.basic.AssetPrice;
import com.r35157.libs.valuetypes.basic.MoneyAmount; import com.r35157.libs.valuetypes.basic.MoneyAmount;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -8,21 +9,21 @@ import java.util.HashMap;
public interface DesiredPositionCalculator { public interface DesiredPositionCalculator {
HashMap<ΩRaydiumLiquidityPoolPositionNftIdΩ, MoneyAmount> calculateRebalancingProposal( HashMap<ΩRaydiumLiquidityPoolPositionNftIdΩ, Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ>> calculateRebalancingProposal(
ΩPriceΩ currentPrice, ΩSyrupPriceΩ currentPrice,
MoneyAmount totalReadyAmountMintA, ΩSolanaAmountΩ totalReadyAmountMintA,
MoneyAmount totalReadyAmountMintB ΩSyrupAmountΩ totalReadyAmountMintB
); );
Pair<MoneyAmount, MoneyAmount> calculateTotalDistributedSums(); Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> calculateTotalDistributedSums();
Pair<MoneyAmount, MoneyAmount> calculateLockedSums(ΩPriceΩ currentPrice); Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> calculateLockedSums(ΩSyrupPriceΩ currentPrice);
Pair<MoneyAmount, MoneyAmount> calculateRedistributableSums( Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> calculateRedistributableSums(
ΩPriceΩ currentPrice, ΩSyrupPriceΩ currentPrice,
MoneyAmount inactiveInAccountMintA, ΩSolanaAmountΩ inactiveInAccountMintA,
MoneyAmount inactiveInAccountMintB, ΩSolanaAmountΩ reservedForBurnMintA,
MoneyAmount reservedForBurnMintA, ΩSyrupAmountΩ inactiveInAccountMintB,
MoneyAmount reservedForBurnMintB ΩSyrupAmountΩ reservedForBurnMintB
); );
} }
@@ -4,8 +4,10 @@ import com.r35157.libs.basic.Pair;
import com.fanitas.evelyn.raydium.RaydiumLiquidityPoolPositionConcentrated; import com.fanitas.evelyn.raydium.RaydiumLiquidityPoolPositionConcentrated;
import com.fanitas.evelyn.core.DesiredPositionCalculator; import com.fanitas.evelyn.core.DesiredPositionCalculator;
import com.r35157.libs.valuetypes.basic.AssetPrice;
import com.r35157.libs.valuetypes.basic.CurrencyType; import com.r35157.libs.valuetypes.basic.CurrencyType;
import com.r35157.libs.valuetypes.basic.MoneyAmount; import com.r35157.libs.valuetypes.basic.MoneyAmount;
import org.jetbrains.annotations.NotNull;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
@@ -27,37 +29,38 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator
} }
@Override @Override
public HashMap<ΩRaydiumLiquidityPoolPositionNftIdΩ, MoneyAmount> calculateRebalancingProposal( public HashMap<ΩRaydiumLiquidityPoolPositionNftIdΩ, Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ>> calculateRebalancingProposal(
ΩPriceΩ currentPrice, ΩSyrupPriceΩ currentPrice,
MoneyAmount totalReadyAmountMintA, ΩSolanaAmountΩ totalReadyAmountMintA,
MoneyAmount totalReadyAmountMintB ΩSyrupAmountΩ totalReadyAmountMintB
) { ) {
List<ΩPriceΩ> intervalStarts = getSortedPositionIntervalFromValues(liquidityProviderPositions); List<ΩSyrupPriceΩ> intervalStarts = getSortedPositionIntervalFromValues(liquidityProviderPositions);
List<ΩPriceΩ> intervalEnds = getSortedPositionIntervalToValues(liquidityProviderPositions); List<ΩSyrupPriceΩ> intervalEnds = getSortedPositionIntervalToValues(liquidityProviderPositions);
int indexOfPositionWithAnEndPriceBeforePositionWithCurrentPrice = lookupIndexOfLastLessThanOrEqual( int indexOfPositionWithAnEndPriceBeforePositionWithCurrentPrice = lookupIndexOfLastLessThanOrEqual(
intervalStarts, intervalStarts,
currentPrice currentPrice
); );
ΩPriceΩ lastLessThanOrEqual = intervalEnds.get(indexOfPositionWithAnEndPriceBeforePositionWithCurrentPrice); ΩSyrupPriceΩ lastLessThanOrEqual = intervalEnds.get(indexOfPositionWithAnEndPriceBeforePositionWithCurrentPrice);
HashMap<ΩRaydiumLiquidityPoolPositionIdΩ, MoneyAmount> desiredPositions = new HashMap<>(); HashMap<ΩRaydiumLiquidityPoolPositionIdΩ, MoneyAmount> desiredPositions = new HashMap<>();
boolean beforeNonZeroPos = true; boolean beforeNonZeroPos = true;
for (int i = 0; i < intervalStarts.size(); i++) { for (int i = 0; i < intervalStarts.size(); i++) {
ΩPriceΩ iStart = intervalStarts.get(i); ΩSyrupPriceΩ iStart = intervalStarts.get(i);
ΩPriceΩ iEnd = intervalEnds.get(i); ΩSyrupPriceΩ iEnd = intervalEnds.get(i);
ΩAmountΩ dp = calculateDesiredPositionSizeForInterval( ΩSolanaAmountΩ desiredSolanaPosSize = null; // TODO: Support this
ΩSyrupAmountΩ desiredSyrupPosSize = calculateDesiredPositionSizeForInterval(
currentPrice, currentPrice,
totalReadyAmountMintB.amount(), totalReadyAmountMintB,
iStart, iStart,
iEnd, iEnd,
lastLessThanOrEqual lastLessThanOrEqual
); );
int desiredPositionSign = dp.compareTo(ZERO); int desiredPositionSign = desiredSyrupPosSize.amount().compareTo(ZERO);
if (desiredPositionSign > 0) { if (desiredPositionSign > 0) {
beforeNonZeroPos = false; beforeNonZeroPos = false;
@@ -65,21 +68,31 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator
break; break;
} }
MoneyAmount desiredPositionSize = new MoneyAmount( ΩSolanaAmountΩ desiredPositionSizeSolana = new MoneyAmount(
dp, desiredSolanaPosSize,
totalReadyAmountMintA.currencyType()
);
ΩSyrupAmountΩ desiredPositionSizeSyrup = new MoneyAmount(
desiredSyrupPosSize,
totalReadyAmountMintB.currencyType() totalReadyAmountMintB.currencyType()
); );
Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> desiredPositionSizes =
new Pair<>(desiredPositionSizeSolana, desiredPositionSizeSyrup);
RaydiumLiquidityPoolPositionConcentrated position = getPositionByStartPriceA(iStart); RaydiumLiquidityPoolPositionConcentrated position = getPositionByStartPriceA(iStart);
// TODO: What if null is return above?
ΩRaydiumLiquidityPoolPositionNftIdΩ poolPositionId = position.nftId(); ΩRaydiumLiquidityPoolPositionNftIdΩ poolPositionId = position.nftId();
desiredPositions.put(poolPositionId, desiredPositionSize);
desiredPositions.put(poolPositionId, desiredPositionSizes);
} }
return desiredPositions; return desiredPositions;
} }
@Override @Override
public Pair<MoneyAmount, MoneyAmount> calculateTotalDistributedSums() { public Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> calculateTotalDistributedSums() {
Collection<RaydiumLiquidityPoolPositionConcentrated> c = liquidityProviderPositions.values(); Collection<RaydiumLiquidityPoolPositionConcentrated> c = liquidityProviderPositions.values();
if(c.isEmpty()) { if(c.isEmpty()) {
@@ -107,7 +120,7 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator
} }
@Override @Override
public Pair<MoneyAmount, MoneyAmount> calculateLockedSums(ΩPriceΩ currentPrice) { public Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> calculateLockedSums(ΩSyrupPriceΩ currentPrice) {
List<ΩPriceΩ> ascendingPrices = getSortedPositionIntervalFromValues(liquidityProviderPositions); List<ΩPriceΩ> ascendingPrices = getSortedPositionIntervalFromValues(liquidityProviderPositions);
ΩAmountΩ sumA = ZERO; ΩAmountΩ sumA = ZERO;
ΩAmountΩ sumB = ZERO; ΩAmountΩ sumB = ZERO;
@@ -116,7 +129,7 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator
CurrencyType ctA = null; CurrencyType ctA = null;
CurrencyType ctB = null; CurrencyType ctB = null;
int index = lookupIndexOfFirstGreaterThanOrEqual(ascendingPrices, currentPrice); int index = lookupIndexOfFirstGreaterThanOrEqual(ascendingPrices, currentPrice.price());
if(index >= 0) { if(index >= 0) {
ΩPriceΩ startPriceOfStartPos = ascendingPrices.get(index); ΩPriceΩ startPriceOfStartPos = ascendingPrices.get(index);
@@ -137,12 +150,12 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator
} }
@Override @Override
public Pair<MoneyAmount, MoneyAmount> calculateRedistributableSums( public Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> calculateRedistributableSums(
ΩPriceΩ currentPrice, ΩSyrupPriceΩ currentPrice,
MoneyAmount inactiveInAccountMintA, ΩSolanaAmountΩ inactiveInAccountMintA,
MoneyAmount inactiveInAccountMintB, ΩSolanaAmountΩ reservedForBurnMintA,
MoneyAmount reservedForBurnMintA, ΩSyrupAmountΩ inactiveInAccountMintB,
MoneyAmount reservedForBurnMintB ΩSyrupAmountΩ reservedForBurnMintB
) { ) {
List<ΩPriceΩ> ascendingValues = getSortedPositionIntervalFromValues(liquidityProviderPositions); List<ΩPriceΩ> ascendingValues = getSortedPositionIntervalFromValues(liquidityProviderPositions);
ΩAmountΩ redistSumA = ZERO; ΩAmountΩ redistSumA = ZERO;
@@ -150,7 +163,7 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator
CurrencyType ctA = null; CurrencyType ctA = null;
CurrencyType ctB = null; CurrencyType ctB = null;
int index = lookupIndexOfLastLessThan(ascendingValues, currentPrice); int index = lookupIndexOfLastLessThan(ascendingValues, currentPrice.price());
if(index >= 0) { if(index >= 0) {
ΩPriceΩ startPriceOfStartPos = ascendingValues.get(index); ΩPriceΩ startPriceOfStartPos = ascendingValues.get(index);
@@ -174,8 +187,6 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator
return new Pair<>(ma, mb); return new Pair<>(ma, mb);
} }
// TODO: Can this be deleted?
private MoneyAmount add(ΩAmountΩ a, ΩAmountΩ b, CurrencyType ct) { private MoneyAmount add(ΩAmountΩ a, ΩAmountΩ b, CurrencyType ct) {
MoneyAmount ma = new MoneyAmount(a.add(b), ct); MoneyAmount ma = new MoneyAmount(a.add(b), ct);
return ma; return ma;
@@ -194,7 +205,7 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator
return a.subtract(b.amount()); return a.subtract(b.amount());
} }
private RaydiumLiquidityPoolPositionConcentrated getPositionByStartPriceA(ΩPriceΩ startPriceA) { private RaydiumLiquidityPoolPositionConcentrated getPositionByStartPriceA(@NotNull ΩPriceΩ startPriceA) {
for (RaydiumLiquidityPoolPositionConcentrated candidate : liquidityProviderPositions.values()) { for (RaydiumLiquidityPoolPositionConcentrated candidate : liquidityProviderPositions.values()) {
if (candidate.priceRange().from().compareTo(startPriceA) == 0) { if (candidate.priceRange().from().compareTo(startPriceA) == 0) {
return candidate; return candidate;
@@ -204,12 +215,12 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator
return null; return null;
} }
private ΩAmountΩ calculateDesiredPositionSizeForInterval( private ΩSyrupAmountΩ calculateDesiredPositionSizeForInterval(
ΩPriceΩ currentPrice, @NotNull ΩSyrupPriceΩ currentPrice,
ΩAmountΩ totalSyrupToDistribution, @NotNull ΩSolanaAmountΩ totalSyrupToDistribution,
ΩPriceΩ intervalPriceFrom, @NotNull ΩSyrupPriceΩ intervalPriceFrom,
ΩPriceΩ intervalPriceTo, @NotNull ΩSyrupPriceΩ intervalPriceTo,
ΩPriceΩ lookupPrice @NotNull ΩSyrupPriceΩ lookupPrice
) { ) {
ΩPriceΩ curveStartPrice = currentPrice.multiply( ΩPriceΩ curveStartPrice = currentPrice.multiply(
ONE.subtract(curveWidth, MC), ONE.subtract(curveWidth, MC),
@@ -256,97 +267,109 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator
return dps; return dps;
} }
private static int lookupIndexOfFirstGreaterThan(List<ΩPriceΩ> ascendingPrices, ΩPriceΩ searchPrice) { private static int lookupIndexOfFirstGreaterThan(
int index = -1; @NotNull List<ΩSyrupPriceΩ> ascendingPrices,
@NotNull ΩSyrupPriceΩ searchPrice
for (int i = ascendingPrices.size() - 1; i >= 0; i--) {
BigDecimal p = ascendingPrices.get(i);
if (p.compareTo(searchPrice) <= 0) {
break;
} else {
index = i;
}
}
if (index == -1) {
throw new IllegalArgumentException(
"No position has a greater start price than a position containing '" + searchPrice + "'"
);
}
return index;
}
private static int lookupIndexOfFirstGreaterThanOrEqual(List<ΩPriceΩ> ascendingPrices, ΩPriceΩ searchPrice) {
int index = -1;
for (int i = ascendingPrices.size() - 1; i >= 0; i--) {
BigDecimal p = ascendingPrices.get(i);
if (p.compareTo(searchPrice) <= 0) {
break;
} else {
index = i;
}
}
if (index == -1) {
throw new IllegalArgumentException(
"No position has a greater start price than a position containing '" + searchPrice + "'"
);
}
return index;
}
private static int lookupIndexOfLastLessThanOrEqual(List<ΩPriceΩ> ascendingPrices, ΩPriceΩ searchPrice) {
int index = -1;
for (int i = 0; i < ascendingPrices.size() - 1; i++) {
ΩPriceΩ p = ascendingPrices.get(i);
if (p.compareTo(searchPrice) < 0) {
index = i - 1;
} else {
break;
}
}
return index;
}
private static int lookupIndexOfLastLessThan(List<ΩPriceΩ> ascendingPrices, ΩPriceΩ searchPrice) {
int index = -1;
for (int i = 0; i < ascendingPrices.size() - 1; i++) {
ΩPriceΩ p = ascendingPrices.get(i);
if (p.compareTo(searchPrice) < 0) {
index = i - 1;
} else {
break;
}
}
return index;
}
private static List<ΩPriceΩ> getSortedPositionIntervalFromValues(
Map<ΩRaydiumLiquidityPoolPositionNftIdΩ, RaydiumLiquidityPoolPositionConcentrated> liquidityPositions
) { ) {
List<ΩPriceΩ> result = new ArrayList<>(liquidityPositions.size()); int index = -1;
for (int i = ascendingPrices.size() - 1; i >= 0; i--) {
BigDecimal p = ascendingPrices.get(i);
if (p.compareTo(searchPrice) <= 0) {
break;
} else {
index = i;
}
}
if (index == -1) {
throw new IllegalArgumentException(
"No position has a greater start price than a position containing '" + searchPrice + "'"
);
}
return index;
}
private static int lookupIndexOfFirstGreaterThanOrEqual(
@NotNull List<ΩSyrupPriceΩ> ascendingPrices,
@NotNull ΩSyrupPriceΩ searchPrice
) {
int index = -1;
for (int i = ascendingPrices.size() - 1; i >= 0; i--) {
BigDecimal p = ascendingPrices.get(i);
if (p.compareTo(searchPrice) <= 0) {
break;
} else {
index = i;
}
}
if (index == -1) {
throw new IllegalArgumentException(
"No position has a greater start price than a position containing '" + searchPrice + "'"
);
}
return index;
}
private static int lookupIndexOfLastLessThanOrEqual(
@NotNull List<ΩSyrupPriceΩ> ascendingPrices,
@NotNull ΩSyrupPriceΩ searchPrice
) {
int index = -1;
for (int i = 0; i < ascendingPrices.size() - 1; i++) {
ΩSyrupPriceΩ p = ascendingPrices.get(i);
if (p.compareTo(searchPrice) < 0) {
index = i - 1;
} else {
break;
}
}
return index;
}
private static int lookupIndexOfLastLessThan(
@NotNull List<ΩSyrupPriceΩ> ascendingPrices,
@NotNull ΩSyrupPriceΩ searchPrice
) {
int index = -1;
for (int i = 0; i < ascendingPrices.size() - 1; i++) {
ΩPriceΩ p = ascendingPrices.get(i);
if (p.compareTo(searchPrice) < 0) {
index = i - 1;
} else {
break;
}
}
return index;
}
private static @NotNull List<ΩSyrupPriceΩ> getSortedPositionIntervalFromValues(
@NotNull Map<ΩRaydiumLiquidityPoolPositionNftIdΩ,
@NotNull RaydiumLiquidityPoolPositionConcentrated> liquidityPositions
) {
List<ΩSyrupPriceΩ> result = new ArrayList<>(liquidityPositions.size());
for (RaydiumLiquidityPoolPositionConcentrated position : liquidityPositions.values()) { for (RaydiumLiquidityPoolPositionConcentrated position : liquidityPositions.values()) {
result.add(position.priceRange().from()); result.add(position.priceRange().from());
} }
result.sort(ΩPriceΩ::compareTo); result.sort(ΩSyrupPriceΩ::compareTo);
return result; return result;
} }
private static List<ΩPriceΩ> getSortedPositionIntervalToValues( private static @NotNull List<ΩSyrupPriceΩ> getSortedPositionIntervalToValues(
Map<ΩRaydiumLiquidityPoolPositionNftIdΩ, RaydiumLiquidityPoolPositionConcentrated> liquidityPositions @NotNull Map<ΩRaydiumLiquidityPoolPositionNftIdΩ, RaydiumLiquidityPoolPositionConcentrated> liquidityPositions
) { ) {
List<ΩPriceΩ> result = new ArrayList<>(liquidityPositions.size()); List<ΩSyrupPriceΩ> result = new ArrayList<>(liquidityPositions.size());
for (RaydiumLiquidityPoolPositionConcentrated position : liquidityPositions.values()) { for (RaydiumLiquidityPoolPositionConcentrated position : liquidityPositions.values()) {
result.add(position.priceRange().to()); result.add(position.priceRange().to());
} }
result.sort(ΩPriceΩ::compareTo); result.sort(ΩSyrupPriceΩ::compareTo);
return result; return result;
} }
@@ -10,7 +10,11 @@ import com.r35157.libs.raydium.RaydiumLiquidityPoolPrice;
import com.r35157.libs.solana.SPLTokenHolding; import com.r35157.libs.solana.SPLTokenHolding;
import com.r35157.libs.solana.SolanaBlockChain; import com.r35157.libs.solana.SolanaBlockChain;
import com.r35157.libs.solana.SolanaConstants; import com.r35157.libs.solana.SolanaConstants;
import com.r35157.libs.solana.valuetypes.WellKnownCurrencyTypes;
import com.r35157.libs.valuetypes.basic.AssetPrice;
import com.r35157.libs.valuetypes.basic.CurrencyType;
import com.r35157.libs.valuetypes.basic.MoneyAmount; import com.r35157.libs.valuetypes.basic.MoneyAmount;
import com.r35157.libs.valuetypes.basic.TradingPair;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.*; import java.util.*;
@@ -18,6 +22,8 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import static com.r35157.libs.solana.valuetypes.WellKnownCurrencyTypes.SOLANA;
import static com.r35157.libs.solana.valuetypes.WellKnownCurrencyTypes.SYRUPUSDC;
import static com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram.SPL_TOKEN_PROGRAM; import static com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram.SPL_TOKEN_PROGRAM;
import static com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram.TOKEN_2022_PROGRAM; import static com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram.TOKEN_2022_PROGRAM;
@@ -55,10 +61,21 @@ public class EvelynImpl implements Evelyn {
return ma; return ma;
} }
private MoneyAmount add(MoneyAmount a, MoneyAmount b) {
if(a.currencyType() != b.currencyType()) {
String errTxt = "Cannot add " + b.currencyType()
+ " to " + a.currencyType();
throw new IllegalArgumentException(errTxt);
}
MoneyAmount ma = new MoneyAmount(a.amount().add(b.amount()), a.currencyType());
return ma;
}
private MoneyAmount subtract(MoneyAmount a, MoneyAmount b) { private MoneyAmount subtract(MoneyAmount a, MoneyAmount b) {
if(a.currencyType() != b.currencyType()) { if(a.currencyType() != b.currencyType()) {
String errTxt = "Cannot subtract " + b.currencyType().name() String errTxt = "Cannot subtract " + b.currencyType()
+ " from " + a.currencyType().name(); + " from " + a.currencyType();
throw new IllegalArgumentException(errTxt); throw new IllegalArgumentException(errTxt);
} }
@@ -80,16 +97,18 @@ public class EvelynImpl implements Evelyn {
} }
private void handleRebalancingProposal() { private void handleRebalancingProposal() {
HashMap<ΩRaydiumLiquidityPoolPositionNftIdΩ, MoneyAmount> rebalancingProposal;
try { try {
ΩSolanaAddressΩ solanaAddressForEvelynIOU = state.getSolanaAddressForEvelynIOU(); ΩSolanaAddressΩ solanaAddressForEvelynIOU = state.getSolanaAddressForEvelynIOU();
ΩSPLMintAddressΩ syrupUSDCMintAddr = SolanaConstants.SPL_TOKEN_SYRUPUSDC; ΩSPLMintAddressΩ syrupUSDCMintAddr = SolanaConstants.SPL_TOKEN_SYRUPUSDC;
AssetPrice currentPriceFromRaydium;
ΩSyrupPriceΩ currentPriceFromChain;
SPLTokenHolding syrupHolding = getSPLHolding(solanaAddressForEvelynIOU, syrupUSDCMintAddr); SPLTokenHolding syrupHolding = getSPLHolding(solanaAddressForEvelynIOU, syrupUSDCMintAddr);
ΩAmountΩ inactiveInAccountSyrup = syrupHolding.uiAmount();
ΩPriceΩ currentPriceFromRaydium; ΩSyrupAmountΩ inactiveInAccountSyrup = new ΩSyrupAmountΩ(
MoneyAmount currentPriceFromChain; syrupHolding.uiAmount(),
SYRUPUSDC.getCurrencyType()
);
while(true) { while(true) {
state.update(); state.update();
@@ -105,21 +124,27 @@ public class EvelynImpl implements Evelyn {
); );
concentratedResult = cFuture.get(); concentratedResult = cFuture.get();
currentPriceFromRaydium = concentratedResult.amount().amount();
currentPriceFromRaydium = new AssetPrice(
concentratedResult.price(),
tpSolSyrup
);
} }
currentPriceFromChain = raydium.fetchPoolPrice(state.getRaydiumPoolId());
ΩPriceΩ price = raydium.fetchPoolPrice(state.getRaydiumPoolId());
currentPriceFromChain = new AssetPrice(price, tpSolSyrup);
System.out.println("Iteration interval: " + (state.getIterationInterval() / 1000) + " secs"); System.out.println("Iteration interval: " + (state.getIterationInterval() / 1000) + " secs");
System.out.println("Solana balances:"); System.out.println("Solana balances:");
ΩSolanaAmountΩ solBalanceEvelyn = solanaChain.getBalanceInSolana(state.getSolanaAddressForEvelynIOU()); ΩSolanaAmountΩ solBalanceEvelynAccount = solanaChain.getBalanceInSolana(state.getSolanaAddressForEvelynIOU());
System.out.println(" Evelyn: " + solBalanceEvelyn.amount()); System.out.println(" Evelyn account: " + solBalanceEvelynAccount.amount());
ΩSolanaAmountΩ solBalanceBurn = solanaChain.getBalanceInSolana(state.getSolanaAddressForEvelynIOUBurner()); ΩSolanaAmountΩ solBalanceBurnAccount = solanaChain.getBalanceInSolana(state.getSolanaAddressForEvelynIOUBurner());
System.out.println(" Burn: " + solBalanceBurn.amount()); System.out.println(" Burn account: " + solBalanceBurnAccount.amount());
System.out.println("SYRUP owned by Evelyn: " + state.getSyrupOwnedByEvelyn().amount()); System.out.println("SYRUP owned by Evelyn: " + state.getSyrupOwnedByEvelyn().amount());
System.out.println("SYRUP inactive on Evelyn account: " + inactiveInAccountSyrup); System.out.println("SYRUP inactive on Evelyn account: " + inactiveInAccountSyrup);
System.out.println("Pool price: " + currentPriceFromRaydium + "/" + currentPriceFromChain.amount()); System.out.println("Pool price: " + currentPriceFromRaydium + "/" + currentPriceFromChain);
Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> totalDistributedSums = Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> totalDistributedSums =
desiredPositionCalculator.calculateTotalDistributedSums(); desiredPositionCalculator.calculateTotalDistributedSums();
@@ -129,7 +154,7 @@ public class EvelynImpl implements Evelyn {
+ " / " + totalDistributedSumSyrup); + " / " + totalDistributedSumSyrup);
Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> amountsLocked = Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> amountsLocked =
desiredPositionCalculator.calculateLockedSums(currentPriceFromChain.amount()); desiredPositionCalculator.calculateLockedSums(currentPriceFromChain);
ΩSolanaAmountΩ amountLockedSolana = amountsLocked.left(); ΩSolanaAmountΩ amountLockedSolana = amountsLocked.left();
ΩSyrupAmountΩ amountLockedSyrup = amountsLocked.right(); ΩSyrupAmountΩ amountLockedSyrup = amountsLocked.right();
System.out.println("Total amount locked due to HIGH price: " + amountLockedSolana); System.out.println("Total amount locked due to HIGH price: " + amountLockedSolana);
@@ -138,29 +163,35 @@ public class EvelynImpl implements Evelyn {
ΩSyrupAmountΩ totalAmountSyrup = add(totalDistributedSumSyrup, inactiveInAccountSyrup); ΩSyrupAmountΩ totalAmountSyrup = add(totalDistributedSumSyrup, inactiveInAccountSyrup);
ΩSyrupAmountΩ reservedForBurnSyrup = subtract(totalAmountSyrup, state.getSyrupOwnedByEvelyn()); ΩSyrupAmountΩ reservedForBurnSyrup = subtract(totalAmountSyrup, state.getSyrupOwnedByEvelyn());
ΩSolanaAmountΩ readyForBurnSolana = subtract(solBalanceEvelyn, SOFT_LOW_LIMIT_SOLANA_BALANCE); ΩSolanaAmountΩ readyForBurnSolana = subtract(solBalanceEvelynAccount, SOFT_LOW_LIMIT_SOLANA_BALANCE);
System.out.println("Amount reserved for burn: " System.out.println("Amount reserved for burn: "
+ "Solana: " + readyForBurnSolana + "Solana: " + readyForBurnSolana
+ ", Syrup:" + reservedForBurnSyrup); + ", Syrup:" + reservedForBurnSyrup);
Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> syrupTotalReadyAmount = desiredPositionCalculator.calculateRedistributableSums( Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> totalReadyAmount = desiredPositionCalculator
currentPriceFromChain.amount(), .calculateRedistributableSums(
new ΩSyrupAmountΩ(inactiveInAccountSyrup, readyForBurnSolana.currencyType()), // TODO: Wow! This is not pretty! Stealing cyrrenctType from another object. Oh dear! currentPriceFromChain,
reservedForBurnSyrup solBalanceEvelynAccount,
); amountZeroSolana, // We do not burn Solana
System.out.println("Total amount of Syrup ready for distribution: " + syrupTotalReadyAmount.amount()); inactiveInAccountSyrup,
reservedForBurnSyrup
);
ΩSolanaAmountΩ solTotalReadyAmount = totalReadyAmount.left();
ΩSyrupAmountΩ syrupTotalReadyAmount = totalReadyAmount.right();
System.out.println("Total amount of Syrup ready for distribution: " + syrupTotalReadyAmount);
rebalancingProposal = desiredPositionCalculator.calculateRebalancingProposal( HashMap<ΩRaydiumLiquidityPoolPositionNftIdΩ, Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ>> rebalancingProposal =
currentPriceFromChain.amount(), desiredPositionCalculator.calculateRebalancingProposal(
syrupTotalReadyAmount currentPriceFromChain,
); solTotalReadyAmount,
syrupTotalReadyAmount
);
Map<ΩRaydiumLiquidityPoolPositionNftIdΩ, RaydiumLiquidityPoolPositionConcentrated> liquidityPositions = state.getLiquidityPositions(); Map<ΩRaydiumLiquidityPoolPositionNftIdΩ, RaydiumLiquidityPoolPositionConcentrated> liquidityPositions =
state.getLiquidityPositions();
Map<String, Triblet> diffs = calculateDiffs( Map<ΩRaydiumLiquidityPoolPositionNftIdΩ, Triblet<ΩSyrupAmountΩ, ΩSyrupAmountΩ, ΩSyrupAmountΩ>> diffs =
rebalancingProposal, calculateDiffs(rebalancingProposal, liquidityPositions);
liquidityPositions
);
String minKey = diffs.entrySet().stream() String minKey = diffs.entrySet().stream()
.min(Comparator.comparing(entry -> entry.getValue().diff())) .min(Comparator.comparing(entry -> entry.getValue().diff()))
@@ -205,24 +236,39 @@ public class EvelynImpl implements Evelyn {
} }
} }
private HashMap<String, Triblet> calculateDiffs( private HashMap<ΩRaydiumLiquidityPoolPositionNftIdΩ, Triblet<ΩSyrupAmountΩ, ΩSyrupAmountΩ, ΩSyrupAmountΩ>> calculateDiffs(
Map<ΩRaydiumLiquidityPoolPositionNftIdΩ, ΩSyrupAmountΩ> rebalancingProposal, Map<ΩRaydiumLiquidityPoolPositionNftIdΩ, Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ>> rebalancingProposal,
Map<ΩRaydiumLiquidityPoolPositionNftIdΩ, RaydiumLiquidityPoolPositionConcentrated> liquidityProviderPositionMap Map<ΩRaydiumLiquidityPoolPositionNftIdΩ, RaydiumLiquidityPoolPositionConcentrated> liquidityProviderPositionMap
) { ) {
HashMap<String, Triblet> diffs = new HashMap<>(rebalancingProposal.size()); HashMap<ΩRaydiumLiquidityPoolPositionNftIdΩ, Triblet<ΩSyrupAmountΩ, ΩSyrupAmountΩ, ΩSyrupAmountΩ>> diffs =
new HashMap<>(rebalancingProposal.size());
for(String nftId : rebalancingProposal.keySet()) { for(ΩRaydiumLiquidityPoolPositionNftIdΩ nftId : rebalancingProposal.keySet()) {
RaydiumLiquidityPoolPositionConcentrated pos = liquidityProviderPositionMap.get(nftId); RaydiumLiquidityPoolPositionConcentrated pos = liquidityProviderPositionMap.get(nftId);
ΩAmountΩ currentlyAmountAdded = pos.amountMintB().amount(); ΩAmountΩ currentlyAmountAdded = pos.amountMintB().amount();
ΩSyrupAmountΩ suggestedAmount = rebalancingProposal.get(nftId); ΩSyrupAmountΩ suggestedAmount = rebalancingProposal.get(nftId);
ΩAmountΩ diff = subtract(currentlyAmountAdded, suggestedAmount); ΩAmountΩ diff = subtract(currentlyAmountAdded, suggestedAmount);
Triblet t = new Triblet(currentlyAmountAdded, suggestedAmount.amount(), diff); Triblet<ΩSyrupAmountΩ, ΩSyrupAmountΩ, ΩSyrupAmountΩ> t = new Triblet<>(
currentlyAmountAdded,
suggestedAmount.amount(),
diff
);
diffs.put(nftId, t); diffs.put(nftId, t);
} }
return diffs; return diffs;
} }
private final static TradingPair tpSolSyrup = new TradingPair(
SOLANA.getCurrencyType(),
SYRUPUSDC.getCurrencyType()
);
private final static ΩSolanaAmountΩ amountZeroSolana = new ΩSolanaAmountΩ(
BigDecimal.ZERO,
SOLANA.getCurrencyType()
);
private final ΩAmountΩ SOFT_LOW_LIMIT_SOLANA_BALANCE = new BigDecimal("0.1"); private final ΩAmountΩ SOFT_LOW_LIMIT_SOLANA_BALANCE = new BigDecimal("0.1");
private final State state; private final State state;
@@ -1,6 +1,7 @@
package com.fanitas.evelyn.raydium; package com.fanitas.evelyn.raydium;
import com.r35157.libs.valuetypes.basic.MoneyAmount; import com.r35157.libs.valuetypes.basic.MoneyAmount;
import org.jetbrains.annotations.NotNull;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -21,11 +22,11 @@ import java.math.BigDecimal;
* @param accountingInfo the amount added to and borrowed from the position * @param accountingInfo the amount added to and borrowed from the position
*/ */
public record RaydiumLiquidityPoolPositionConcentrated( public record RaydiumLiquidityPoolPositionConcentrated(
ΩRaydiumLiquidityPoolConcentratedIdΩ poolId, @NotNull ΩRaydiumLiquidityPoolConcentratedIdΩ poolId,
ΩRaydiumLiquidityPoolPositionNftIdΩ nftId, @NotNull ΩRaydiumLiquidityPoolPositionNftIdΩ nftId,
PriceRange priceRange, @NotNull PriceRange priceRange,
MoneyAmount amountMintA, @NotNull MoneyAmount amountMintA,
MoneyAmount amountMintB, @NotNull MoneyAmount amountMintB,
RaydiumLiquidityPoolPositionAccounting accountingInfo @NotNull RaydiumLiquidityPoolPositionAccounting accountingInfo
) { ) {
} }
@@ -1,18 +1,18 @@
package com.r35157.libs.raydium; package com.r35157.libs.raydium;
import com.r35157.libs.valuetypes.basic.MoneyAmount; import java.math.BigDecimal;
/** /**
* Represents the price of a Raydium liquidity pool. * Represents the current price of the tokens in a Raydium liquidity pool.
* *
* <p>The pool id identifies the Raydium liquidity pool for which the price applies. * <p>The 'poolId' identifies the Raydium liquidity pool for which the current token price applies.
* The amount contains the price value returned or calculated for that pool.</p> * The 'price' represents the price value returned or calculated for that pool.</p>
* *
* @param poolId the Raydium liquidity pool id that the price belongs to * @param poolId the Raydium liquidity pool id that the price belongs to
* @param amount the price amount for the liquidity pool * @param price the price for a token in the liquidity pool
*/ */
public record RaydiumLiquidityPoolPrice( public record RaydiumLiquidityPoolPrice(
ΩRaydiumLiquidityPoolIdΩ poolId, ΩRaydiumLiquidityPoolIdΩ poolId,
MoneyAmount amount ΩPriceΩ price
) { ) {
} }
@@ -5,11 +5,24 @@ import org.jetbrains.annotations.NotNull;
import java.math.BigDecimal; import java.math.BigDecimal;
public record AssetPrice( public record AssetPrice(
ΩPriceΩ price, @NotNull ΩPriceΩ price,
CurrencyType currencyType @NotNull TradingPair tradingPair
) { ) implements Comparable<AssetPrice> {
@Override
public int compareTo(@NotNull AssetPrice other) {
if (!tradingPair.equals(other.tradingPair)) {
throw new IllegalArgumentException(
"AssetPrice values with different trading pairs cannot be compared: "
+ tradingPair + " and " + other.tradingPair
);
}
return price.compareTo(other.price);
}
@Override @Override
public @NotNull String toString() { public @NotNull String toString() {
return price + " " + currencyType(); return price + " " + tradingPair.quote();
} }
} }
@@ -1,6 +1,13 @@
package com.r35157.libs.valuetypes.basic; package com.r35157.libs.valuetypes.basic;
import org.jetbrains.annotations.NotNull;
public record TradingPair( public record TradingPair(
CurrencyType base, // The thing you are buying or selling. CurrencyType base, // The thing you are buying or selling.
CurrencyType quote // The currency/unit used to price the base asset. CurrencyType quote // The currency/unit used to price the base asset.
) { } ) {
@Override
public @NotNull String toString() {
return base.symbol() + "_" + quote.symbol();
}
}