Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add wallet analyzer #95

Merged
merged 2 commits into from
Jul 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions src/components/configuration/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,7 @@ const Configuration = ({
setLoading(true);
getConfiguration()
.then((d) => {
const globalConfig = d?.data
? (yaml.parse(d.data) as GlobalConfig)
: initialConfiguration;
const globalConfig = d ?? initialConfiguration;

setGroupUSD(globalConfig.configs.groupUSD);
setQuerySize(globalConfig.configs.querySize || 10);
Expand All @@ -145,7 +143,6 @@ const Configuration = ({
}))
.value()
);
console.log(globalConfig);

setWallets(
_(globalConfig)
Expand Down
74 changes: 51 additions & 23 deletions src/components/index/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Chart as ChartJS,
registerables,
CategoryScale,
LinearScale,
PointElement,
Expand Down Expand Up @@ -27,7 +28,7 @@ import {
TopCoinsPercentageChangeData,
TopCoinsRankData,
} from "../../middlelayers/types";
import { useContext, useEffect, useMemo, useState } from "react";
import { useContext, useEffect, useState } from "react";
import {
queryAssetChange,
queryTopCoinsPercentageChangeData,
Expand All @@ -45,8 +46,10 @@ import {
} from "../../middlelayers/configuration";
import { autoSyncData } from "../../middlelayers/cloudsync";
import { getDefaultCurrencyRate } from "../../middlelayers/currency";
import _ from "lodash";

ChartJS.register(
...registerables,
ArcElement,
CategoryScale,
LinearScale,
Expand All @@ -71,6 +74,7 @@ const App = () => {

const [showMenu, setShowMenu] = useState(false);
const [activeMenu, setActiveMenu] = useState("overview");
// const [activeMenu, setActiveMenu] = useState("wallets");

const [latestAssetsPercentageData, setLatestAssetsPercentageData] = useState(
[] as LatestAssetsPercentageData
Expand Down Expand Up @@ -117,7 +121,7 @@ const App = () => {
if (
lastSize.width === windowSize.width &&
lastSize.height === windowSize.height &&
activeMenu === "overview"
(activeMenu === "overview" || activeMenu === "wallets")
) {
resizeAllCharts();
}
Expand All @@ -133,10 +137,32 @@ const App = () => {
}, [showMenu]);

function resizeAllCharts() {
const overviewsCharts = [
"Trend of Asset",
"Trend of Coin",
"Percentage of Assets",
"Change of Top Coins",
"Trend of Top Coins Rank",
];
const walletsCharts = ["Percentage of Wallet"];
let chartsTitles: string[] = [];
if (activeMenu === "overview") {
chartsTitles = overviewsCharts;
} else if (activeMenu === "wallets") {
chartsTitles = walletsCharts;
}
console.log("resizing all charts");

for (const id in Chart.instances) {
Chart.instances[id].resize();
const text = Chart.instances[id].options.plugins?.title?.text as
| string
| undefined;
if (
!text ||
!!_(chartsTitles).find((x) => text === x || text.startsWith(x))
) {
Chart.instances[id].resize();
}
}
}

Expand Down Expand Up @@ -189,10 +215,10 @@ const App = () => {
}

useEffect(() => {
if (activeMenu === "overview") {
if (activeMenu === "overview" || activeMenu === "wallets") {
setTimeout(() => {
resizeAllCharts();
}, resizeDelay);
}, resizeDelay / 2);
}
}, [activeMenu]);

Expand All @@ -206,7 +232,7 @@ const App = () => {
<ul>
<li onClick={() => onMenuClicked("overview")}>Overview</li>
<li onClick={() => onMenuClicked("comparison")}>Comparison</li>
{/* <li onClick={() => onMenuClicked("wallets")}>Wallets</li> */}
<li onClick={() => onMenuClicked("wallets")}>Wallets</li>
</ul>
</div>
);
Expand Down Expand Up @@ -259,22 +285,24 @@ const App = () => {
</div>
</div>
<div onMouseDown={closeMenu}>
<div
id="overview"
style={{
display: activeMenu === "overview" ? "block" : "none",
}}
>
<Overview
currency={currentCurrency}
latestAssetsPercentageData={latestAssetsPercentageData}
assetChangeData={assetChangeData}
totalValueData={totalValueData}
coinsAmountAndValueChangeData={coinsAmountAndValueChangeData}
topCoinsRankData={topCoinsRankData}
topCoinsPercentageChangeData={topCoinsPercentageChangeData}
/>
</div>
{activeMenu === "overview" && (
<div
id="overview"
// style={{
// display: activeMenu === "overview" ? "block" : "none",
// }}
>
<Overview
currency={currentCurrency}
latestAssetsPercentageData={latestAssetsPercentageData}
assetChangeData={assetChangeData}
totalValueData={totalValueData}
coinsAmountAndValueChangeData={coinsAmountAndValueChangeData}
topCoinsRankData={topCoinsRankData}
topCoinsPercentageChangeData={topCoinsPercentageChangeData}
/>
</div>
)}

{activeMenu === "comparison" && (
<div id="comparison">
Expand All @@ -284,7 +312,7 @@ const App = () => {

{activeMenu === "wallets" && (
<div id="wallets">
<WalletAnalyzer />
<WalletAnalyzer currency={currentCurrency}/>
</div>
)}
</div>
Expand Down
37 changes: 33 additions & 4 deletions src/components/wallet-analyzer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,37 @@
const App = ({}: {}) => {
import { useContext, useEffect, useState } from "react";
import { queryWalletAssetsPercentage } from "../../middlelayers/charts";
import WalletAssetsPercentage from "../wallet-assets-percentage";
import { LoadingContext } from "../../App";
import {
CurrencyRateDetail,
WalletAssetsPercentageData,
} from "../../middlelayers/types";

const App = ({ currency }: { currency: CurrencyRateDetail }) => {
const { setLoading } = useContext(LoadingContext);
const [walletAssetsPercentage, setWalletAssetsPercentage] =
useState<WalletAssetsPercentageData>([]);

useEffect(() => {
setLoading(true);
loadAllDataAsync().finally(() => setLoading(false));
}, []);

async function loadAllDataAsync() {
console.log("loading all wallet data...");
const wap = await queryWalletAssetsPercentage();
setWalletAssetsPercentage(wap);
}

return (
<div className="wallet-analyzer">
<h1>TODO</h1>
</div>
<>
<h1>Wallet Analyzer</h1>
<WalletAssetsPercentage
data={walletAssetsPercentage}
currency={currency}
/>
<hr className="nice-hr" />
</>
);
};

Expand Down
76 changes: 76 additions & 0 deletions src/components/wallet-assets-percentage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Bar } from "react-chartjs-2";
import { useWindowSize } from "../../utils/hook";
import {
CurrencyRateDetail,
WalletAssetsPercentageData,
} from "../../middlelayers/types";
import { currencyWrapper } from '../../utils/currency'

const App = ({
data,
currency,
}: {
data: WalletAssetsPercentageData;
currency: CurrencyRateDetail;
}) => {
const size = useWindowSize();

const options = {
maintainAspectRatio: false,
responsive: false,
indexAxis: "y",
barPercentage: 0.9,
plugins: {
title: { display: true, text: "Percentage of Wallet" },
legend: {
display: false,
},
datalabels: {
display: "auto",
align: "top",
offset: 6,
formatter: (
value: number,
context: {
chart: { data: { labels: { [x: string]: any } } };
dataIndex: string | number;
}
) => {
// const label = context.chart.data.labels[context.dataIndex];
return `${currency.symbol}${value.toFixed(2)}`;
},
},
},
};

function lineData() {
return {
labels: data.map((d) => d.walletAlias || d.wallet),
datasets: [
{
alias: "y",
fill: false,
data: data.map((d) => currencyWrapper(currency)(d.value)),
borderColor: data.map((d) => d.chartColor),
backgroundColor: data.map((d) => d.chartColor),
borderWidth: 1,
},
],
};
}

return (
<div>
<div
style={{
height: Math.max((size.height || 100) / 2, 400),
}}
>
{/* <Pie options={options as any} data={lineData()} /> */}
<Bar options={options as any} data={lineData()} />
</div>
</div>
);
};

export default App;
42 changes: 35 additions & 7 deletions src/middlelayers/charts.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import _ from 'lodash'
import yaml from 'yaml'
import { generateRandomColors } from '../utils/color'
import { getDatabase, saveCoinsToDatabase } from './database'
import { AssetChangeData, AssetModel, CoinData, CoinsAmountAndValueChangeData, HistoricalData, LatestAssetsPercentageData, TopCoinsPercentageChangeData, TopCoinsRankData, TotalValueData } from './types'
import { AssetChangeData, AssetModel, CoinData, CoinsAmountAndValueChangeData, HistoricalData, LatestAssetsPercentageData, WalletAssetsPercentageData, TopCoinsPercentageChangeData, TopCoinsRankData, TotalValueData } from './types'

import { loadPortfolios, queryCoinPrices } from './data'
import { getConfiguration } from './configuration'
import { calculateTotalValue } from './datafetch/utils/coins'
import { CexConfig, TokenConfig, WalletCoin } from './datafetch/types'
import { WalletCoin } from './datafetch/types'
import { timestampToDate } from '../utils/date'
import { listWalletAliases } from './wallet'

const STABLE_COIN = ["USDT", "USDC", "BUSD", "DAI", "TUSD", "PAX"]

Expand All @@ -23,11 +23,10 @@ async function queryCoinsData(): Promise<(WalletCoin & {
price: number,
usdValue: number,
})[]> {
const configModel = await getConfiguration()
if (!configModel) {
const config = await getConfiguration()
if (!config) {
throw new Error("no configuration found,\n please add configuration first")
}
const config = yaml.parse(configModel.data) as CexConfig & TokenConfig
const assets = await loadPortfolios(config)
const priceMap = await queryCoinPrices(_(assets).map("symbol").push("USDT").uniq().value())

Expand All @@ -38,7 +37,7 @@ async function queryCoinsData(): Promise<(WalletCoin & {
_(assets).groupBy('wallet').forEach((coins, wallet) => {
const usdAmount = _(coins).filter(c => STABLE_COIN.includes(c.symbol)).map(c => c.amount).sum()
const removedUSDCoins = _(coins).filter(c => !STABLE_COIN.includes(c.symbol)).value()
lastAssets = _(lastAssets).filter(a=>a.wallet !== wallet).concat(removedUSDCoins).value()
lastAssets = _(lastAssets).filter(a => a.wallet !== wallet).concat(removedUSDCoins).value()
if (usdAmount > 0) {
lastAssets.push({
symbol: "USDT",
Expand Down Expand Up @@ -137,6 +136,35 @@ export async function queryTotalValue(): Promise<TotalValueData> {
}
}

export async function queryWalletAssetsPercentage(): Promise<WalletAssetsPercentageData> {
const assets = (await queryAssets(1))[0]
// check if there is wallet column
const hasWallet = _(assets).find(a => !!a.wallet)
if (!assets || !hasWallet) {
return []
}
const walletAssets = _(assets).groupBy('wallet')
.map((walletAssets, wallet) => {
const total = _(walletAssets).sumBy("value")
return {
wallet,
total,
}
}).value()
const total = _(walletAssets).sumBy("total") || 0.0001
const wallets = _(walletAssets).map('wallet').uniq().compact().value()
const backgroundColors = generateRandomColors(wallets.length)
const walletAliases = await listWalletAliases(wallets)

return _(walletAssets).map((wa, idx) => ({
wallet: wa.wallet,
walletAlias: walletAliases[wa.wallet],
chartColor: `rgba(${backgroundColors[idx].R}, ${backgroundColors[idx].G}, ${backgroundColors[idx].B}, 1)`,
percentage: wa.total / total * 100,
value: wa.total,
})).sortBy("percentage").reverse().value()
}

export async function queryTopCoinsRank(size = 10): Promise<TopCoinsRankData> {

const assets = groupAssetModelsListBySymbol(await queryAssets(size) || [])
Expand Down
Loading
Loading