Skip to content

Commit

Permalink
Include fetched USD prices in the CSV
Browse files Browse the repository at this point in the history
  • Loading branch information
mcintyre94 committed Dec 3, 2024
1 parent 4c023b8 commit e3a3fc5
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 8 deletions.
125 changes: 119 additions & 6 deletions src/routes/trades-csv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,22 @@ import {
Deposit,
AmountToDisplay,
StrategyType,
FetchedTokenPrices,
Timestamp,
FetchedTokenPriceKey,
} from "../types";
import { Address, Signature, StringifiedNumber } from "@solana/web3.js";
import {
numberDisplay,
numberDisplayAlreadyAdjustedForDecimals,
} from "../number-display";
import BigDecimal from "js-big-decimal";
import { roundTimestampToMinuteBoundary } from "../token-prices";

type InputData = {
events: (Deposit | Trade)[];
mints: MintData[];
fetchedTokenPrices: FetchedTokenPrices;
};

type ShortStrategyType = "DCA" | "VA" | "LO";
Expand All @@ -27,12 +32,16 @@ type CSVTradeDataRow = {
inTokenName: string;
inTokenSymbol: string;
inAmount: StringifiedNumber;
inAmountUsd: StringifiedNumber;
outTokenAddress: Address;
outTokenName: string;
outTokenSymbol: string;
outAmount: StringifiedNumber;
outAmountUsd: StringifiedNumber;
outAmountFee: StringifiedNumber;
outAmountFeeUsd: StringifiedNumber;
outAmountNet: StringifiedNumber;
outAmountNetUsd: StringifiedNumber;
transactionSignature: Signature;
strategyType: ShortStrategyType;
strategyKey: Address;
Expand All @@ -45,6 +54,7 @@ type CSVDepositDataRow = {
inTokenName: string;
inTokenSymbol: string;
inAmount: StringifiedNumber;
inAmountUsd: StringifiedNumber;
transactionSignature: Signature;
strategyType: ShortStrategyType;
strategyKey: Address;
Expand All @@ -63,12 +73,16 @@ export function convertToCSV(
"inTokenName",
"inTokenSymbol",
"inAmount",
"inAmountUsd",
"outTokenAddress",
"outTokenName",
"outTokenSymbol",
"outAmount",
"outAmountUsd",
"outAmountFee",
"outAmountFeeUsd",
"outAmountNet",
"outAmountNetUsd",
"transactionSignature",
"strategyType",
"strategyKey",
Expand All @@ -80,12 +94,16 @@ export function convertToCSV(
"In Token Name",
"In Token Symbol",
"In Amount",
"In Amount USD",
"Out Token Address",
"Out Token Name",
"Out Token Symbol",
"Out Amount (gross)",
"Out Amount",
"Out Amount USD",
"Out Amount (fee)",
"Out Amount (fee) USD",
"Out Amount (net)",
"Out Amount (net) USD",
"Transaction Signature",
"Strategy Type",
"Strategy Key",
Expand Down Expand Up @@ -136,7 +154,40 @@ function getShortStrategyType(strategyType: StrategyType): ShortStrategyType {
throw new Error(`Unknown strategy type: ${strategyType}`);
}

function csvDataForTrade(trade: Trade, mints: MintData[]): CSVTradeDataRow {
function getUsdPrice(
mintAddress: Address,
timestamp: Timestamp,
fetchedTokenPrices: FetchedTokenPrices,
): number | undefined {
const roundedTimestamp = roundTimestampToMinuteBoundary(timestamp);
const key: FetchedTokenPriceKey = `${mintAddress}-${roundedTimestamp}`;
return fetchedTokenPrices[key];
}

function getUsdAmount(
price: number | undefined,
mintAmount: AmountToDisplay,
mintData: MintData | undefined,
): StringifiedNumber {
if (!price) {
return "" as StringifiedNumber;
}
if (mintData || mintAmount.adjustedForDecimals) {
const mintDecimals = mintData?.decimals ?? 0;
const tokenAmount = getAmountBigDecimal(mintAmount, mintDecimals);
return tokenAmount
.multiply(new BigDecimal(price))
.round(6)
.getPrettyValue() as StringifiedNumber;
}
return "" as StringifiedNumber;
}

function csvDataForTrade(
trade: Trade,
mints: MintData[],
fetchedTokenPrices: FetchedTokenPrices,
): CSVTradeDataRow {
const inputMintData = mints.find((mint) => mint.address === trade.inputMint);
const outputMintData = mints.find(
(mint) => mint.address === trade.outputMint,
Expand Down Expand Up @@ -170,19 +221,55 @@ function csvDataForTrade(trade: Trade, mints: MintData[]): CSVTradeDataRow {
outputAmountNetBigDecimal.getPrettyValue() as StringifiedNumber;
}

const timestamp = Math.floor(new Date(trade.date).getTime() / 1000);

const inUsdPrice = getUsdPrice(
trade.inputMint,
timestamp,
fetchedTokenPrices,
);
const inAmountUsd = getUsdAmount(
inUsdPrice,
trade.inputAmount,
inputMintData,
);

const outUsdPrice = getUsdPrice(
trade.outputMint,
timestamp,
fetchedTokenPrices,
);

const outAmountUsd = getUsdAmount(
outUsdPrice,
trade.outputAmount,
outputMintData,
);

const outAmountFeeUsd = getUsdAmount(outUsdPrice, trade.fee, outputMintData);

const outAmountNetUsd = new BigDecimal(outAmountUsd)
.subtract(new BigDecimal(outAmountFeeUsd))
.round(6)
.getPrettyValue() as StringifiedNumber;

return {
kind: "trade",
timestamp: Math.floor(new Date(trade.date).getTime() / 1000),
timestamp,
inTokenAddress: trade.inputMint,
inTokenName: inputMintData?.name ?? "",
inTokenSymbol: inputMintData?.symbol ?? "",
inAmount: inputAmountFormatted as StringifiedNumber,
inAmountUsd: inAmountUsd as StringifiedNumber,
outTokenAddress: trade.outputMint,
outTokenName: outputMintData?.name ?? "",
outTokenSymbol: outputMintData?.symbol ?? "",
outAmount: outputAmountFormatted as StringifiedNumber,
outAmountUsd: outAmountUsd as StringifiedNumber,
outAmountFee: outputAmountFeeFormatted as StringifiedNumber,
outAmountFeeUsd: outAmountFeeUsd as StringifiedNumber,
outAmountNet: outputAmountNetFormatted as StringifiedNumber,
outAmountNetUsd: outAmountNetUsd as StringifiedNumber,
transactionSignature: trade.transactionSignature,
strategyType: getShortStrategyType(trade.strategyType),
strategyKey: trade.strategyKey,
Expand All @@ -192,21 +279,39 @@ function csvDataForTrade(trade: Trade, mints: MintData[]): CSVTradeDataRow {
function csvDataForDeposit(
deposit: Deposit,
mints: MintData[],
fetchedTokenPrices: FetchedTokenPrices,
): CSVDepositDataRow {
console.log({ fetchedTokenPrices });

const inputMintData = mints.find(
(mint) => mint.address === deposit.inputMint,
);
const inputAmountFormatted = inputMintData
? getAmountFormatted(deposit.inputAmount, inputMintData.decimals)
: "";

const timestamp = Math.floor(new Date(deposit.date).getTime() / 1000);

const usdPrice = getUsdPrice(
deposit.inputMint,
timestamp,
fetchedTokenPrices,
);

const inAmountUsd = getUsdAmount(
usdPrice,
deposit.inputAmount,
inputMintData,
);

return {
kind: "deposit",
timestamp: Math.floor(new Date(deposit.date).getTime() / 1000),
timestamp,
inTokenAddress: deposit.inputMint,
inTokenName: inputMintData?.name ?? "",
inTokenSymbol: inputMintData?.symbol ?? "",
inAmount: inputAmountFormatted as StringifiedNumber,
inAmountUsd: inAmountUsd as StringifiedNumber,
transactionSignature: deposit.transactionSignature,
strategyType: getShortStrategyType(deposit.strategyType),
strategyKey: deposit.strategyKey,
Expand All @@ -219,9 +324,17 @@ export async function action({ request }: ActionFunctionArgs) {
const csvData: (CSVTradeDataRow | CSVDepositDataRow)[] = inputData.events.map(
(event) => {
if (event.kind === "deposit") {
return csvDataForDeposit(event, inputData.mints);
return csvDataForDeposit(
event,
inputData.mints,
inputData.fetchedTokenPrices,
);
} else {
return csvDataForTrade(event, inputData.mints);
return csvDataForTrade(
event,
inputData.mints,
inputData.fetchedTokenPrices,
);
}
},
);
Expand Down
14 changes: 12 additions & 2 deletions src/routes/trades.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -326,9 +326,15 @@ type DownloadButtonProps = {
events: (Trade | Deposit)[];
mints: MintData[];
userAddress: Address;
fetchedTokenPrices: FetchedTokenPrices;
};

function DownloadButton({ events, mints, userAddress }: DownloadButtonProps) {
function DownloadButton({
events,
mints,
userAddress,
fetchedTokenPrices,
}: DownloadButtonProps) {
const fetcher = useFetcher();
const isLoading = fetcher.state === "loading";
const downloadLinkRef = useRef<HTMLAnchorElement>(null);
Expand All @@ -338,14 +344,15 @@ function DownloadButton({ events, mints, userAddress }: DownloadButtonProps) {
JSON.stringify({
events,
mints,
fetchedTokenPrices,
}),
{
method: "post",
action: "/trades/csv",
encType: "application/json",
},
);
}, [events, mints]);
}, [events, mints, fetchedTokenPrices]);

useEffect(() => {
if (fetcher.data && fetcher.state === "idle") {
Expand Down Expand Up @@ -1198,6 +1205,7 @@ export default function Trades() {
events={events}
mints={mints}
userAddress={userAddress}
fetchedTokenPrices={alreadyFetchedTokenPrices}
/>
</Group>
</Group>
Expand Down Expand Up @@ -1283,3 +1291,5 @@ export default function Trades() {
</>
);
}

// TODO: add USD prices to CSV!

0 comments on commit e3a3fc5

Please sign in to comment.