Skip to content

Commit

Permalink
Add DCA statistics and aggregation methods
Browse files Browse the repository at this point in the history
Introduced JupiterUserDcaStats class for aggregating user's DCA statistics. Enhanced JupiterManager with methods for DCA order filtering, aggregation, and detailed statistics. Updated test cases to validate these new functionalities.
  • Loading branch information
skynetcap committed Oct 2, 2024
1 parent b691a35 commit b7bb8b1
Show file tree
Hide file tree
Showing 4 changed files with 444 additions and 5 deletions.
117 changes: 117 additions & 0 deletions jupiter/src/main/java/com/mmorrell/jupiter/manager/JupiterManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.mmorrell.jupiter.model.*;
import com.mmorrell.jupiter.util.JupiterUtil;
import java.util.Collections;

import lombok.extern.slf4j.Slf4j;
import org.bitcoinj.core.Base58;
import org.p2p.solanaj.core.PublicKey;
Expand All @@ -13,6 +15,8 @@
import org.p2p.solanaj.rpc.types.ProgramAccount;

import java.util.*;
import java.time.Instant;
import java.util.stream.Collectors;

@Slf4j
public class JupiterManager {
Expand All @@ -26,6 +30,7 @@ public JupiterManager() {
this.client = new RpcClient(Cluster.MAINNET);
}


public JupiterManager(RpcClient client) {
this.client = client;
}
Expand Down Expand Up @@ -160,4 +165,116 @@ public List<JupiterDca> getDcaAccounts(PublicKey user) {
return Collections.emptyList();
}
}

public List<JupiterDca> getActiveDcaOrders() {
long now = Instant.now().getEpochSecond();
return getDcaAccounts().stream()
.filter(dca -> dca.getNextCycleAt() > now && dca.getInUsed() < dca.getInDeposited())
.collect(Collectors.toList());
}

public List<JupiterDca> getDcaOrdersByTokenPair(PublicKey inputMint, PublicKey outputMint) {
return getDcaAccounts().stream()
.filter(dca -> dca.getInputMint().equals(inputMint) && dca.getOutputMint().equals(outputMint))
.collect(Collectors.toList());
}

public double getAggregatedDcaVolume(PublicKey inputMint, PublicKey outputMint, long startTime, long endTime) {
return getDcaAccounts().stream()
.filter(dca -> dca.getInputMint().equals(inputMint)
&& dca.getOutputMint().equals(outputMint)
&& dca.getCreatedAt() >= startTime
&& dca.getCreatedAt() <= endTime)
.mapToDouble(dca -> (double) dca.getInDeposited() / Math.pow(10, 6))
.sum();
}

public List<Map.Entry<Map.Entry<PublicKey, PublicKey>, Long>> getMostPopularDcaPairs(int limit) {
Map<Map.Entry<PublicKey, PublicKey>, Long> pairCounts = getDcaAccounts().stream()
.collect(Collectors.groupingBy(
dca -> new AbstractMap.SimpleEntry<>(dca.getInputMint(), dca.getOutputMint()),
Collectors.counting()
));

return pairCounts.entrySet().stream()
.sorted(Map.Entry.<Map.Entry<PublicKey, PublicKey>, Long>comparingByValue().reversed())
.limit(limit)
.collect(Collectors.toList());
}

/**
* Retrieves completed Jupiter DCA orders.
* A DCA order is considered completed if it has fully utilized its deposited amount or has expired.
*
* @return a list of completed JupiterDca objects.
*/
public List<JupiterDca> getCompletedDcaOrders() {
long now = Instant.now().getEpochSecond();
return getDcaAccounts().stream()
.filter(dca -> dca.getInUsed() >= dca.getInDeposited() || dca.getNextCycleAt() <= now)
.collect(Collectors.toList());
}

/**
* Retrieves Jupiter DCA orders created within a specific time range.
*
* @param startTime the start epoch time.
* @param endTime the end epoch time.
* @return a list of JupiterDca objects within the specified time range.
*/
public List<JupiterDca> getDcaOrdersByTimeRange(long startTime, long endTime) {
return getDcaAccounts().stream()
.filter(dca -> dca.getCreatedAt() >= startTime && dca.getCreatedAt() <= endTime)
.collect(Collectors.toList());
}

/**
* Retrieves Jupiter DCA orders sorted by the total deposited amount in descending order.
*
* @return a list of JupiterDca objects sorted by volume.
*/
public List<JupiterDca> getDcaOrdersSortedByVolume() {
return getDcaAccounts().stream()
.sorted(Comparator.comparingLong(JupiterDca::getInDeposited).reversed())
.collect(Collectors.toList());
}

/**
* Retrieves aggregated statistics for a specific user's DCA activities.
*
* @param user the PublicKey of the user.
* @return a UserDcaStats object containing aggregated statistics.
*/
public JupiterUserDcaStats getUserDcaStatistics(PublicKey user) {
List<JupiterDca> userDcas = getDcaAccounts(user);

long totalOrders = userDcas.size();
double totalVolume = userDcas.stream()
.mapToDouble(dca -> (double) dca.getInDeposited() / Math.pow(10, 6))
.sum();
Set<PublicKey> uniqueInputTokens = userDcas.stream()
.map(JupiterDca::getInputMint)
.collect(Collectors.toSet());
Set<PublicKey> uniqueOutputTokens = userDcas.stream()
.map(JupiterDca::getOutputMint)
.collect(Collectors.toSet());

return new JupiterUserDcaStats(totalOrders, totalVolume, uniqueInputTokens, uniqueOutputTokens);
}


/**
* Retrieves the most recent Jupiter DCA orders up to the specified limit.
*
* @param limit the maximum number of recent orders to retrieve.
* @return a list of recent JupiterDca objects.
*/
public List<JupiterDca> getRecentDcaOrders(int limit) {
return getDcaAccounts().stream()
.sorted(Comparator.comparingLong(JupiterDca::getCreatedAt).reversed())
.limit(limit)
.collect(Collectors.toList());
}

// Additional methods can be added here as needed for other use cases.
}
128 changes: 128 additions & 0 deletions jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterDca.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@
import lombok.Data;
import org.p2p.solanaj.core.PublicKey;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
* Represents a Jupiter DCA (Dollar-Cost Averaging) account.
*/
@Data
@Builder
public class JupiterDca {
private static final PublicKey USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
private static final PublicKey USDT_MINT = new PublicKey("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB");
private static final int DECIMALS = 6;

private PublicKey user;
private PublicKey inputMint;
private PublicKey outputMint;
Expand All @@ -33,6 +40,127 @@ public class JupiterDca {
private long createdAt;
private byte bump;

/**
* Checks if the input mint is a stablecoin (USDC or USDT).
*
* @return true if the input mint is USDC or USDT, false otherwise.
*/
public boolean isInputStablecoin() {
return inputMint.equals(USDC_MINT) || inputMint.equals(USDT_MINT);
}

/**
* Checks if the output mint is a stablecoin (USDC or USDT).
*
* @return true if the output mint is USDC or USDT, false otherwise.
*/
public boolean isOutputStablecoin() {
return outputMint.equals(USDC_MINT) || outputMint.equals(USDT_MINT);
}

/**
* Calculates the USD value of the deposited amount.
*
* @return the USD value of the deposited amount, or null if neither input nor output is a stablecoin.
*/
public BigDecimal getInDepositedUsd() {
return isInputStablecoin() ? convertToUsd(inDeposited) :
(isOutputStablecoin() ? convertToUsd(outReceived) : null);
}

/**
* Calculates the USD value of the withdrawn amount.
*
* @return the USD value of the withdrawn amount, or null if neither input nor output is a stablecoin.
*/
public BigDecimal getInWithdrawnUsd() {
return isInputStablecoin() ? convertToUsd(inWithdrawn) :
(isOutputStablecoin() ? convertToUsd(outWithdrawn) : null);
}

/**
* Calculates the USD value of the used amount.
*
* @return the USD value of the used amount, or null if neither input nor output is a stablecoin.
*/
public BigDecimal getInUsedUsd() {
return isInputStablecoin() ? convertToUsd(inUsed) :
(isOutputStablecoin() ? convertToUsd(outReceived) : null);
}

/**
* Calculates the USD value of the amount per cycle.
*
* @return the USD value of the amount per cycle, or null if neither input nor output is a stablecoin.
*/
public BigDecimal getInAmountPerCycleUsd() {
return isInputStablecoin() ? convertToUsd(inAmountPerCycle) : null;
}

/**
* Calculates the total notional value in the original token.
*
* @return the total notional value in the original token.
*/
public BigDecimal getTotalNotional() {
return convertToDecimal(inDeposited);
}

/**
* Calculates the total notional value in USD.
*
* @return the total notional value in USD, or null if neither input nor output is a stablecoin.
*/
public BigDecimal getTotalNotionalUsd() {
return isInputStablecoin() ? convertToUsd(inDeposited) :
(isOutputStablecoin() ? convertToUsd(outReceived) : null);
}

/**
* Calculates the remaining notional value in the original token.
*
* @return the remaining notional value in the original token.
*/
public BigDecimal getRemainingNotional() {
return convertToDecimal(inDeposited - inUsed);
}

/**
* Calculates the remaining notional value in USD.
*
* @return the remaining notional value in USD, or null if neither input nor output is a stablecoin.
*/
public BigDecimal getRemainingNotionalUsd() {
if (isInputStablecoin()) {
return convertToUsd(inDeposited - inUsed);
} else if (isOutputStablecoin()) {
return convertToUsd(outReceived - outWithdrawn);
}
return null;
}

/**
* Converts a token amount to its USD value.
*
* @param amount the token amount to convert.
* @return the USD value of the token amount.
*/
private BigDecimal convertToUsd(long amount) {
return BigDecimal.valueOf(amount)
.divide(BigDecimal.valueOf(Math.pow(10, DECIMALS)), 2, RoundingMode.HALF_UP);
}

/**
* Converts a token amount to its decimal representation.
*
* @param amount the token amount to convert.
* @return the decimal representation of the token amount.
*/
private BigDecimal convertToDecimal(long amount) {
return BigDecimal.valueOf(amount)
.divide(BigDecimal.valueOf(Math.pow(10, DECIMALS)), DECIMALS, RoundingMode.HALF_UP);
}

/**
* Deserializes a byte array into a JupiterDca object.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.mmorrell.jupiter.model;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.Set;
import org.p2p.solanaj.core.PublicKey;

/**
* Data Transfer Object for aggregating a user's DCA statistics.
*/
@Data
@AllArgsConstructor
public class JupiterUserDcaStats {
private long totalOrders;
private double totalVolumeUsd;
private Set<PublicKey> uniqueInputTokens;
private Set<PublicKey> uniqueOutputTokens;
}
Loading

0 comments on commit b7bb8b1

Please sign in to comment.