diff --git a/src/main/tjava/com/fanitas/evelyn/core/DesiredPositionCalculator.tjava b/src/main/tjava/com/fanitas/evelyn/core/DesiredPositionCalculator.tjava index 35b6756..bb9a4e7 100644 --- a/src/main/tjava/com/fanitas/evelyn/core/DesiredPositionCalculator.tjava +++ b/src/main/tjava/com/fanitas/evelyn/core/DesiredPositionCalculator.tjava @@ -1,6 +1,7 @@ package com.fanitas.evelyn.core; import com.r35157.libs.basic.Pair; +import com.r35157.libs.valuetypes.basic.AssetPrice; import com.r35157.libs.valuetypes.basic.MoneyAmount; import java.math.BigDecimal; @@ -8,21 +9,21 @@ import java.util.HashMap; public interface DesiredPositionCalculator { - HashMap<ΩRaydiumLiquidityPoolPositionNftIdΩ, MoneyAmount> calculateRebalancingProposal( - ΩPriceΩ currentPrice, - MoneyAmount totalReadyAmountMintA, - MoneyAmount totalReadyAmountMintB + HashMap<ΩRaydiumLiquidityPoolPositionNftIdΩ, Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ>> calculateRebalancingProposal( + ΩSyrupPriceΩ currentPrice, + ΩSolanaAmountΩ totalReadyAmountMintA, + ΩSyrupAmountΩ totalReadyAmountMintB ); - Pair calculateTotalDistributedSums(); + Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> calculateTotalDistributedSums(); - Pair calculateLockedSums(ΩPriceΩ currentPrice); + Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> calculateLockedSums(ΩSyrupPriceΩ currentPrice); - Pair calculateRedistributableSums( - ΩPriceΩ currentPrice, - MoneyAmount inactiveInAccountMintA, - MoneyAmount inactiveInAccountMintB, - MoneyAmount reservedForBurnMintA, - MoneyAmount reservedForBurnMintB + Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> calculateRedistributableSums( + ΩSyrupPriceΩ currentPrice, + ΩSolanaAmountΩ inactiveInAccountMintA, + ΩSolanaAmountΩ reservedForBurnMintA, + ΩSyrupAmountΩ inactiveInAccountMintB, + ΩSyrupAmountΩ reservedForBurnMintB ); } diff --git a/src/main/tjava/com/fanitas/evelyn/core/impl/ref/DesiredPositionCalculatorImpl.tjava b/src/main/tjava/com/fanitas/evelyn/core/impl/ref/DesiredPositionCalculatorImpl.tjava index a21c3f4..3f93a68 100644 --- a/src/main/tjava/com/fanitas/evelyn/core/impl/ref/DesiredPositionCalculatorImpl.tjava +++ b/src/main/tjava/com/fanitas/evelyn/core/impl/ref/DesiredPositionCalculatorImpl.tjava @@ -4,8 +4,10 @@ import com.r35157.libs.basic.Pair; import com.fanitas.evelyn.raydium.RaydiumLiquidityPoolPositionConcentrated; 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.MoneyAmount; +import org.jetbrains.annotations.NotNull; import java.math.BigDecimal; import java.math.MathContext; @@ -27,37 +29,38 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator } @Override - public HashMap<ΩRaydiumLiquidityPoolPositionNftIdΩ, MoneyAmount> calculateRebalancingProposal( - ΩPriceΩ currentPrice, - MoneyAmount totalReadyAmountMintA, - MoneyAmount totalReadyAmountMintB + public HashMap<ΩRaydiumLiquidityPoolPositionNftIdΩ, Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ>> calculateRebalancingProposal( + ΩSyrupPriceΩ currentPrice, + ΩSolanaAmountΩ totalReadyAmountMintA, + ΩSyrupAmountΩ totalReadyAmountMintB ) { - List<ΩPriceΩ> intervalStarts = getSortedPositionIntervalFromValues(liquidityProviderPositions); - List<ΩPriceΩ> intervalEnds = getSortedPositionIntervalToValues(liquidityProviderPositions); + List<ΩSyrupPriceΩ> intervalStarts = getSortedPositionIntervalFromValues(liquidityProviderPositions); + List<ΩSyrupPriceΩ> intervalEnds = getSortedPositionIntervalToValues(liquidityProviderPositions); int indexOfPositionWithAnEndPriceBeforePositionWithCurrentPrice = lookupIndexOfLastLessThanOrEqual( intervalStarts, currentPrice ); - ΩPriceΩ lastLessThanOrEqual = intervalEnds.get(indexOfPositionWithAnEndPriceBeforePositionWithCurrentPrice); + ΩSyrupPriceΩ lastLessThanOrEqual = intervalEnds.get(indexOfPositionWithAnEndPriceBeforePositionWithCurrentPrice); HashMap<ΩRaydiumLiquidityPoolPositionIdΩ, MoneyAmount> desiredPositions = new HashMap<>(); boolean beforeNonZeroPos = true; for (int i = 0; i < intervalStarts.size(); i++) { - ΩPriceΩ iStart = intervalStarts.get(i); - ΩPriceΩ iEnd = intervalEnds.get(i); + ΩSyrupPriceΩ iStart = intervalStarts.get(i); + ΩSyrupPriceΩ iEnd = intervalEnds.get(i); - ΩAmountΩ dp = calculateDesiredPositionSizeForInterval( + ΩSolanaAmountΩ desiredSolanaPosSize = null; // TODO: Support this + ΩSyrupAmountΩ desiredSyrupPosSize = calculateDesiredPositionSizeForInterval( currentPrice, - totalReadyAmountMintB.amount(), + totalReadyAmountMintB, iStart, iEnd, lastLessThanOrEqual ); - int desiredPositionSign = dp.compareTo(ZERO); + int desiredPositionSign = desiredSyrupPosSize.amount().compareTo(ZERO); if (desiredPositionSign > 0) { beforeNonZeroPos = false; @@ -65,21 +68,31 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator break; } - MoneyAmount desiredPositionSize = new MoneyAmount( - dp, + ΩSolanaAmountΩ desiredPositionSizeSolana = new MoneyAmount( + desiredSolanaPosSize, + totalReadyAmountMintA.currencyType() + ); + + ΩSyrupAmountΩ desiredPositionSizeSyrup = new MoneyAmount( + desiredSyrupPosSize, totalReadyAmountMintB.currencyType() ); + Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> desiredPositionSizes = + new Pair<>(desiredPositionSizeSolana, desiredPositionSizeSyrup); + RaydiumLiquidityPoolPositionConcentrated position = getPositionByStartPriceA(iStart); + // TODO: What if null is return above? ΩRaydiumLiquidityPoolPositionNftIdΩ poolPositionId = position.nftId(); - desiredPositions.put(poolPositionId, desiredPositionSize); + + desiredPositions.put(poolPositionId, desiredPositionSizes); } return desiredPositions; } @Override - public Pair calculateTotalDistributedSums() { + public Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> calculateTotalDistributedSums() { Collection c = liquidityProviderPositions.values(); if(c.isEmpty()) { @@ -107,7 +120,7 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator } @Override - public Pair calculateLockedSums(ΩPriceΩ currentPrice) { + public Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> calculateLockedSums(ΩSyrupPriceΩ currentPrice) { List<ΩPriceΩ> ascendingPrices = getSortedPositionIntervalFromValues(liquidityProviderPositions); ΩAmountΩ sumA = ZERO; ΩAmountΩ sumB = ZERO; @@ -116,7 +129,7 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator CurrencyType ctA = null; CurrencyType ctB = null; - int index = lookupIndexOfFirstGreaterThanOrEqual(ascendingPrices, currentPrice); + int index = lookupIndexOfFirstGreaterThanOrEqual(ascendingPrices, currentPrice.price()); if(index >= 0) { ΩPriceΩ startPriceOfStartPos = ascendingPrices.get(index); @@ -137,12 +150,12 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator } @Override - public Pair calculateRedistributableSums( - ΩPriceΩ currentPrice, - MoneyAmount inactiveInAccountMintA, - MoneyAmount inactiveInAccountMintB, - MoneyAmount reservedForBurnMintA, - MoneyAmount reservedForBurnMintB + public Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> calculateRedistributableSums( + ΩSyrupPriceΩ currentPrice, + ΩSolanaAmountΩ inactiveInAccountMintA, + ΩSolanaAmountΩ reservedForBurnMintA, + ΩSyrupAmountΩ inactiveInAccountMintB, + ΩSyrupAmountΩ reservedForBurnMintB ) { List<ΩPriceΩ> ascendingValues = getSortedPositionIntervalFromValues(liquidityProviderPositions); ΩAmountΩ redistSumA = ZERO; @@ -150,7 +163,7 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator CurrencyType ctA = null; CurrencyType ctB = null; - int index = lookupIndexOfLastLessThan(ascendingValues, currentPrice); + int index = lookupIndexOfLastLessThan(ascendingValues, currentPrice.price()); if(index >= 0) { ΩPriceΩ startPriceOfStartPos = ascendingValues.get(index); @@ -174,8 +187,6 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator return new Pair<>(ma, mb); } - - // TODO: Can this be deleted? private MoneyAmount add(ΩAmountΩ a, ΩAmountΩ b, CurrencyType ct) { MoneyAmount ma = new MoneyAmount(a.add(b), ct); return ma; @@ -194,7 +205,7 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator return a.subtract(b.amount()); } - private RaydiumLiquidityPoolPositionConcentrated getPositionByStartPriceA(ΩPriceΩ startPriceA) { + private RaydiumLiquidityPoolPositionConcentrated getPositionByStartPriceA(@NotNull ΩPriceΩ startPriceA) { for (RaydiumLiquidityPoolPositionConcentrated candidate : liquidityProviderPositions.values()) { if (candidate.priceRange().from().compareTo(startPriceA) == 0) { return candidate; @@ -204,12 +215,12 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator return null; } - private ΩAmountΩ calculateDesiredPositionSizeForInterval( - ΩPriceΩ currentPrice, - ΩAmountΩ totalSyrupToDistribution, - ΩPriceΩ intervalPriceFrom, - ΩPriceΩ intervalPriceTo, - ΩPriceΩ lookupPrice + private ΩSyrupAmountΩ calculateDesiredPositionSizeForInterval( + @NotNull ΩSyrupPriceΩ currentPrice, + @NotNull ΩSolanaAmountΩ totalSyrupToDistribution, + @NotNull ΩSyrupPriceΩ intervalPriceFrom, + @NotNull ΩSyrupPriceΩ intervalPriceTo, + @NotNull ΩSyrupPriceΩ lookupPrice ) { ΩPriceΩ curveStartPrice = currentPrice.multiply( ONE.subtract(curveWidth, MC), @@ -256,97 +267,109 @@ public class DesiredPositionCalculatorImpl implements DesiredPositionCalculator return dps; } - private static int lookupIndexOfFirstGreaterThan(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 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 + private static int lookupIndexOfFirstGreaterThan( + @NotNull List<ΩSyrupPriceΩ> ascendingPrices, + @NotNull ΩSyrupPriceΩ searchPrice ) { - 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()) { result.add(position.priceRange().from()); } - result.sort(ΩPriceΩ::compareTo); + result.sort(ΩSyrupPriceΩ::compareTo); return result; } - private static List<ΩPriceΩ> getSortedPositionIntervalToValues( - Map<ΩRaydiumLiquidityPoolPositionNftIdΩ, RaydiumLiquidityPoolPositionConcentrated> liquidityPositions + private static @NotNull List<ΩSyrupPriceΩ> getSortedPositionIntervalToValues( + @NotNull Map<ΩRaydiumLiquidityPoolPositionNftIdΩ, RaydiumLiquidityPoolPositionConcentrated> liquidityPositions ) { - List<ΩPriceΩ> result = new ArrayList<>(liquidityPositions.size()); + List<ΩSyrupPriceΩ> result = new ArrayList<>(liquidityPositions.size()); for (RaydiumLiquidityPoolPositionConcentrated position : liquidityPositions.values()) { result.add(position.priceRange().to()); } - result.sort(ΩPriceΩ::compareTo); + result.sort(ΩSyrupPriceΩ::compareTo); return result; } diff --git a/src/main/tjava/com/fanitas/evelyn/core/impl/ref/EvelynImpl.tjava b/src/main/tjava/com/fanitas/evelyn/core/impl/ref/EvelynImpl.tjava index d9e94d3..4caee2c 100644 --- a/src/main/tjava/com/fanitas/evelyn/core/impl/ref/EvelynImpl.tjava +++ b/src/main/tjava/com/fanitas/evelyn/core/impl/ref/EvelynImpl.tjava @@ -10,7 +10,11 @@ import com.r35157.libs.raydium.RaydiumLiquidityPoolPrice; import com.r35157.libs.solana.SPLTokenHolding; import com.r35157.libs.solana.SolanaBlockChain; 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.TradingPair; import java.math.BigDecimal; import java.util.*; @@ -18,6 +22,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; 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.TOKEN_2022_PROGRAM; @@ -55,10 +61,21 @@ public class EvelynImpl implements Evelyn { 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) { if(a.currencyType() != b.currencyType()) { - String errTxt = "Cannot subtract " + b.currencyType().name() - + " from " + a.currencyType().name(); + String errTxt = "Cannot subtract " + b.currencyType() + + " from " + a.currencyType(); throw new IllegalArgumentException(errTxt); } @@ -80,16 +97,18 @@ public class EvelynImpl implements Evelyn { } private void handleRebalancingProposal() { - HashMap<ΩRaydiumLiquidityPoolPositionNftIdΩ, MoneyAmount> rebalancingProposal; - try { ΩSolanaAddressΩ solanaAddressForEvelynIOU = state.getSolanaAddressForEvelynIOU(); ΩSPLMintAddressΩ syrupUSDCMintAddr = SolanaConstants.SPL_TOKEN_SYRUPUSDC; + AssetPrice currentPriceFromRaydium; + ΩSyrupPriceΩ currentPriceFromChain; SPLTokenHolding syrupHolding = getSPLHolding(solanaAddressForEvelynIOU, syrupUSDCMintAddr); - ΩAmountΩ inactiveInAccountSyrup = syrupHolding.uiAmount(); - ΩPriceΩ currentPriceFromRaydium; - MoneyAmount currentPriceFromChain; + + ΩSyrupAmountΩ inactiveInAccountSyrup = new ΩSyrupAmountΩ( + syrupHolding.uiAmount(), + SYRUPUSDC.getCurrencyType() + ); while(true) { state.update(); @@ -105,21 +124,27 @@ public class EvelynImpl implements Evelyn { ); 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("Solana balances:"); - ΩSolanaAmountΩ solBalanceEvelyn = solanaChain.getBalanceInSolana(state.getSolanaAddressForEvelynIOU()); - System.out.println(" Evelyn: " + solBalanceEvelyn.amount()); - ΩSolanaAmountΩ solBalanceBurn = solanaChain.getBalanceInSolana(state.getSolanaAddressForEvelynIOUBurner()); - System.out.println(" Burn: " + solBalanceBurn.amount()); + ΩSolanaAmountΩ solBalanceEvelynAccount = solanaChain.getBalanceInSolana(state.getSolanaAddressForEvelynIOU()); + System.out.println(" Evelyn account: " + solBalanceEvelynAccount.amount()); + ΩSolanaAmountΩ solBalanceBurnAccount = solanaChain.getBalanceInSolana(state.getSolanaAddressForEvelynIOUBurner()); + System.out.println(" Burn account: " + solBalanceBurnAccount.amount()); System.out.println("SYRUP owned by Evelyn: " + state.getSyrupOwnedByEvelyn().amount()); 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 = desiredPositionCalculator.calculateTotalDistributedSums(); @@ -129,7 +154,7 @@ public class EvelynImpl implements Evelyn { + " / " + totalDistributedSumSyrup); Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> amountsLocked = - desiredPositionCalculator.calculateLockedSums(currentPriceFromChain.amount()); + desiredPositionCalculator.calculateLockedSums(currentPriceFromChain); ΩSolanaAmountΩ amountLockedSolana = amountsLocked.left(); ΩSyrupAmountΩ amountLockedSyrup = amountsLocked.right(); 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Ω 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: " + "Solana: " + readyForBurnSolana + ", Syrup:" + reservedForBurnSyrup); - Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> syrupTotalReadyAmount = desiredPositionCalculator.calculateRedistributableSums( - currentPriceFromChain.amount(), - new ΩSyrupAmountΩ(inactiveInAccountSyrup, readyForBurnSolana.currencyType()), // TODO: Wow! This is not pretty! Stealing cyrrenctType from another object. Oh dear! - reservedForBurnSyrup - ); - System.out.println("Total amount of Syrup ready for distribution: " + syrupTotalReadyAmount.amount()); + Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ> totalReadyAmount = desiredPositionCalculator + .calculateRedistributableSums( + currentPriceFromChain, + solBalanceEvelynAccount, + amountZeroSolana, // We do not burn Solana + inactiveInAccountSyrup, + reservedForBurnSyrup + ); + ΩSolanaAmountΩ solTotalReadyAmount = totalReadyAmount.left(); + ΩSyrupAmountΩ syrupTotalReadyAmount = totalReadyAmount.right(); + System.out.println("Total amount of Syrup ready for distribution: " + syrupTotalReadyAmount); - rebalancingProposal = desiredPositionCalculator.calculateRebalancingProposal( - currentPriceFromChain.amount(), - syrupTotalReadyAmount - ); + HashMap<ΩRaydiumLiquidityPoolPositionNftIdΩ, Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ>> rebalancingProposal = + desiredPositionCalculator.calculateRebalancingProposal( + currentPriceFromChain, + solTotalReadyAmount, + syrupTotalReadyAmount + ); - Map<ΩRaydiumLiquidityPoolPositionNftIdΩ, RaydiumLiquidityPoolPositionConcentrated> liquidityPositions = state.getLiquidityPositions(); + Map<ΩRaydiumLiquidityPoolPositionNftIdΩ, RaydiumLiquidityPoolPositionConcentrated> liquidityPositions = + state.getLiquidityPositions(); - Map diffs = calculateDiffs( - rebalancingProposal, - liquidityPositions - ); + Map<ΩRaydiumLiquidityPoolPositionNftIdΩ, Triblet<ΩSyrupAmountΩ, ΩSyrupAmountΩ, ΩSyrupAmountΩ>> diffs = + calculateDiffs(rebalancingProposal, liquidityPositions); String minKey = diffs.entrySet().stream() .min(Comparator.comparing(entry -> entry.getValue().diff())) @@ -205,24 +236,39 @@ public class EvelynImpl implements Evelyn { } } - private HashMap calculateDiffs( - Map<ΩRaydiumLiquidityPoolPositionNftIdΩ, ΩSyrupAmountΩ> rebalancingProposal, + private HashMap<ΩRaydiumLiquidityPoolPositionNftIdΩ, Triblet<ΩSyrupAmountΩ, ΩSyrupAmountΩ, ΩSyrupAmountΩ>> calculateDiffs( + Map<ΩRaydiumLiquidityPoolPositionNftIdΩ, Pair<ΩSolanaAmountΩ, ΩSyrupAmountΩ>> rebalancingProposal, Map<ΩRaydiumLiquidityPoolPositionNftIdΩ, RaydiumLiquidityPoolPositionConcentrated> liquidityProviderPositionMap ) { - HashMap 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); ΩAmountΩ currentlyAmountAdded = pos.amountMintB().amount(); ΩSyrupAmountΩ suggestedAmount = rebalancingProposal.get(nftId); Ω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); } 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 State state; diff --git a/src/main/tjava/com/fanitas/evelyn/raydium/RaydiumLiquidityPoolPositionConcentrated.tjava b/src/main/tjava/com/fanitas/evelyn/raydium/RaydiumLiquidityPoolPositionConcentrated.tjava index 390e66a..00e3cc0 100644 --- a/src/main/tjava/com/fanitas/evelyn/raydium/RaydiumLiquidityPoolPositionConcentrated.tjava +++ b/src/main/tjava/com/fanitas/evelyn/raydium/RaydiumLiquidityPoolPositionConcentrated.tjava @@ -1,6 +1,7 @@ package com.fanitas.evelyn.raydium; import com.r35157.libs.valuetypes.basic.MoneyAmount; +import org.jetbrains.annotations.NotNull; import java.math.BigDecimal; @@ -21,11 +22,11 @@ import java.math.BigDecimal; * @param accountingInfo the amount added to and borrowed from the position */ public record RaydiumLiquidityPoolPositionConcentrated( - ΩRaydiumLiquidityPoolConcentratedIdΩ poolId, - ΩRaydiumLiquidityPoolPositionNftIdΩ nftId, - PriceRange priceRange, - MoneyAmount amountMintA, - MoneyAmount amountMintB, - RaydiumLiquidityPoolPositionAccounting accountingInfo + @NotNull ΩRaydiumLiquidityPoolConcentratedIdΩ poolId, + @NotNull ΩRaydiumLiquidityPoolPositionNftIdΩ nftId, + @NotNull PriceRange priceRange, + @NotNull MoneyAmount amountMintA, + @NotNull MoneyAmount amountMintB, + @NotNull RaydiumLiquidityPoolPositionAccounting accountingInfo ) { } \ No newline at end of file diff --git a/src/main/tjava/com/r35157/libs/raydium/RaydiumLiquidityPoolPrice.tjava b/src/main/tjava/com/r35157/libs/raydium/RaydiumLiquidityPoolPrice.tjava index 24bd54d..98f7b03 100644 --- a/src/main/tjava/com/r35157/libs/raydium/RaydiumLiquidityPoolPrice.tjava +++ b/src/main/tjava/com/r35157/libs/raydium/RaydiumLiquidityPoolPrice.tjava @@ -1,18 +1,18 @@ 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. * - *

The pool id identifies the Raydium liquidity pool for which the price applies. - * The amount contains the price value returned or calculated for that pool.

+ *

The 'poolId' identifies the Raydium liquidity pool for which the current token price applies. + * The 'price' represents the price value returned or calculated for that pool.

* * @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( ΩRaydiumLiquidityPoolIdΩ poolId, - MoneyAmount amount + ΩPriceΩ price ) { } \ No newline at end of file diff --git a/src/main/tjava/com/r35157/libs/valuetypes/basic/AssetPrice.tjava b/src/main/tjava/com/r35157/libs/valuetypes/basic/AssetPrice.tjava index cf4ce6b..1e2b66e 100644 --- a/src/main/tjava/com/r35157/libs/valuetypes/basic/AssetPrice.tjava +++ b/src/main/tjava/com/r35157/libs/valuetypes/basic/AssetPrice.tjava @@ -5,11 +5,24 @@ import org.jetbrains.annotations.NotNull; import java.math.BigDecimal; public record AssetPrice( - ΩPriceΩ price, - CurrencyType currencyType -) { + @NotNull ΩPriceΩ price, + @NotNull TradingPair tradingPair +) implements Comparable { + + @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 public @NotNull String toString() { - return price + " " + currencyType(); + return price + " " + tradingPair.quote(); } -} +} \ No newline at end of file diff --git a/src/main/tjava/com/r35157/libs/valuetypes/basic/TradingPair.tjava b/src/main/tjava/com/r35157/libs/valuetypes/basic/TradingPair.tjava index c028ff6..bd6c671 100644 --- a/src/main/tjava/com/r35157/libs/valuetypes/basic/TradingPair.tjava +++ b/src/main/tjava/com/r35157/libs/valuetypes/basic/TradingPair.tjava @@ -1,6 +1,13 @@ package com.r35157.libs.valuetypes.basic; +import org.jetbrains.annotations.NotNull; + public record TradingPair( CurrencyType base, // The thing you are buying or selling. CurrencyType quote // The currency/unit used to price the base asset. -) { } +) { + @Override + public @NotNull String toString() { + return base.symbol() + "_" + quote.symbol(); + } +}