From f251754b0501ef129ea879b22ebf42732ca53b57 Mon Sep 17 00:00:00 2001 From: readme-bot Date: Sat, 2 Dec 2023 16:20:48 +0300 Subject: [PATCH] fetch coin prices when load portfolio --- src/middlelayers/charts.ts | 7 +-- src/middlelayers/data.ts | 5 ++- src/middlelayers/database.ts | 9 ++-- .../datafetch/coins/cex/binance.ts | 14 +++--- src/middlelayers/datafetch/coins/cex/cex.ts | 44 ++++++++++++++----- src/middlelayers/datafetch/coins/cex/gate.ts | 17 ++++++- src/middlelayers/datafetch/coins/cex/okex.ts | 20 ++++++++- .../datafetch/coins/cex/others.ts | 2 +- src/middlelayers/datafetch/utils/coins.ts | 3 +- src/middlelayers/types.d.ts | 6 +++ 10 files changed, 91 insertions(+), 36 deletions(-) diff --git a/src/middlelayers/charts.ts b/src/middlelayers/charts.ts index f152933..29bfe15 100644 --- a/src/middlelayers/charts.ts +++ b/src/middlelayers/charts.ts @@ -1,7 +1,7 @@ import _ from 'lodash' import { generateRandomColors } from '../utils/color' import { getDatabase, saveCoinsToDatabase } from './database' -import { AssetChangeData, AssetModel, CoinData, CoinsAmountAndValueChangeData, HistoricalData, LatestAssetsPercentageData, PNLData, TopCoinsPercentageChangeData, TopCoinsRankData, TotalValueData } from './types' +import { AssetChangeData, AssetModel, CoinData, CoinsAmountAndValueChangeData, HistoricalData, LatestAssetsPercentageData, PNLData, TopCoinsPercentageChangeData, TopCoinsRankData, TotalValueData, WalletCoinUSD } from './types' import { loadPortfolios, queryCoinPrices } from './data' import { getConfiguration } from './configuration' @@ -22,10 +22,7 @@ export async function refreshAllData() { await saveCoinsToDatabase(coins) } -async function queryCoinsData(): Promise<(WalletCoin & { - price: number, - usdValue: number, -})[]> { +async function queryCoinsData(): Promise<(WalletCoinUSD)[]> { const config = await getConfiguration() if (!config) { throw new Error("no configuration found,\n please add configuration first") diff --git a/src/middlelayers/data.ts b/src/middlelayers/data.ts index cac96bc..77b4e1d 100644 --- a/src/middlelayers/data.ts +++ b/src/middlelayers/data.ts @@ -38,7 +38,8 @@ export async function loadPortfolios(config: CexConfig & TokenConfig): Promise { - const anas = [ERC20ProAnalyzer, CexAnalyzer, SOLAnalyzer, OthersAnalyzer, BTCAnalyzer, DOGEAnalyzer] + // const anas = [ERC20ProAnalyzer, CexAnalyzer, SOLAnalyzer, OthersAnalyzer, BTCAnalyzer, DOGEAnalyzer] + const anas = [CexAnalyzer] const coinLists = await bluebird.map(anas, async ana => { const a = new ana(config) @@ -58,6 +59,8 @@ async function loadPortfoliosByConfig(config: CexConfig & TokenConfig): Promise< }, { concurrency: anas.length, }) + console.log(coinLists); + const assets = combineCoinLists(coinLists) return assets } diff --git a/src/middlelayers/database.ts b/src/middlelayers/database.ts index 658dd7b..f604999 100644 --- a/src/middlelayers/database.ts +++ b/src/middlelayers/database.ts @@ -1,8 +1,8 @@ import _ from "lodash" import Database from "tauri-plugin-sql-api" import { v4 as uuidv4 } from 'uuid' -import { CoinModel, WalletCoin } from './datafetch/types' -import { AssetModel } from './types' +import { CoinModel } from './datafetch/types' +import { AssetModel, WalletCoinUSD } from './types' import { ASSETS_TABLE_NAME } from './charts' import md5 from 'md5' @@ -19,10 +19,7 @@ export async function getDatabase(): Promise { } // skip where value is less than 1 -export async function saveCoinsToDatabase(coins: (WalletCoin & { - price: number, - usdValue: number, -})[]) { +export async function saveCoinsToDatabase(coins: WalletCoinUSD[]) { const db = await getDatabase() return saveToDatabase(db, _(coins).map(t => ({ diff --git a/src/middlelayers/datafetch/coins/cex/binance.ts b/src/middlelayers/datafetch/coins/cex/binance.ts index 5e41f78..f935e3d 100644 --- a/src/middlelayers/datafetch/coins/cex/binance.ts +++ b/src/middlelayers/datafetch/coins/cex/binance.ts @@ -36,21 +36,21 @@ export class BinanceExchange implements Exchanger { return invoke("query_binance_balance", { apiKey: this.apiKey, apiSecret: this.secret }) } - async fetchCoinsPrice(symbols: string[]): Promise<{ [k: string]: number }> { + async fetchCoinsPrice(): Promise<{ [k: string]: number }> { // https://api.binance.com/api/v3/ticker/price const allPrices = await sendHttpRequest<{ symbol: string price: string }[]>("GET", "https://api.binance.com/api/v3/ticker/price") - const allPricesMap = _(allPrices).keyBy("symbol").mapValues(v => parseFloat(v.price)).value() + const suffix = "USDT" - const resInUSDT = _(symbols).map(s => ({ - symbol: s, - price: allPricesMap[s.toUpperCase() + "USDT"], - })).mapKeys("symbol").mapValues("price").value() + const allPricesMap = _(allPrices).filter(p=>p.symbol.endsWith(suffix)).map(p =>({ + symbol: p.symbol.replace(suffix, ""), + price: parseFloat(p.price) + })).keyBy("symbol").mapValues("price").value() - return resInUSDT + return allPricesMap } async verifyConfig(): Promise { diff --git a/src/middlelayers/datafetch/coins/cex/cex.ts b/src/middlelayers/datafetch/coins/cex/cex.ts index b83b85e..de7b6b5 100644 --- a/src/middlelayers/datafetch/coins/cex/cex.ts +++ b/src/middlelayers/datafetch/coins/cex/cex.ts @@ -17,9 +17,9 @@ export interface Exchanger { // key is coin symbol, value is amount fetchTotalBalance(): Promise<{ [k: string]: number }> - // return coins price in exchange by symbol + // return coins price in exchange // key is coin symbol, value is price in usdt - fetchCoinsPrice(symbols: string[]): Promise<{ [k: string]: number }> + fetchCoinsPrice(): Promise<{ [k: string]: number }> verifyConfig(): Promise } @@ -80,30 +80,52 @@ export class CexAnalyzer implements Analyzer { async verifyConfigs(): Promise { const verifyResults = await bluebird.map(this.exchanges, async ex => { return await ex.verifyConfig() - }, { - concurrency: 2, }) return _(verifyResults).every() } async loadPortfolio(): Promise { + // key is exchange name, value is prices + const cacheCoinPrices = _(await bluebird.map(_(this.exchanges).uniqBy(ex => ex.getExchangeName()).value(), async ex => { + const pricesMap = await ex.fetchCoinsPrice() + return { + exChangeName: ex.getExchangeName(), + pricesMap, + } + })).keyBy("exChangeName").mapValues("pricesMap").value() + + const getPrice = (ex: string, symbol: string): { value: number, base: string } | undefined => { + const pm = cacheCoinPrices[ex] + if (!pm) { + return undefined + } + if (symbol === "USDT") { + return { + value: 1, + base: "usdt", + } + } + const price = pm[symbol] + if (!price) { + return undefined + } + return { + value: price, + base: "usdt", + } + } + const coinLists = await bluebird.map(this.exchanges, async ex => { const portfolio = await this.fetchTotalBalance(ex) - const prices = await ex.fetchCoinsPrice(_(portfolio).keys().value()) // filter all keys are capital const coins = filterCoinsInPortfolio(ex.getIdentity(), portfolio) return _(coins).map(c => ({ ...c, - price: { - value: prices[c.symbol], - base: "usdt", - }, + price: getPrice(ex.getExchangeName(), c.symbol), } as WalletCoin) ).value() - }, { - concurrency: 2, }) return _(coinLists).flatten().value() diff --git a/src/middlelayers/datafetch/coins/cex/gate.ts b/src/middlelayers/datafetch/coins/cex/gate.ts index 7c34a30..ab9783a 100644 --- a/src/middlelayers/datafetch/coins/cex/gate.ts +++ b/src/middlelayers/datafetch/coins/cex/gate.ts @@ -72,8 +72,21 @@ export class GateExchange implements Exchanger { return _(resp).reduce((acc, v) => _.mergeWith(acc, v, (a, b) => (a || 0) + (b || 0)), {}) } - async fetchCoinsPrice(symbols: string[]): Promise<{ [k: string]: number }> { - return {} + async fetchCoinsPrice(): Promise<{ [k: string]: number }> { + // https://api.gateio.ws/api/v4/spot/tickers + const allPrices = await sendHttpRequest<{ + currency_pair: string + last: string + }[]>("GET", this.endpoint + "/api/v4/spot/tickers") + + const suffix = "_USDT" + + const allPricesMap = _(allPrices).filter(p=>p.currency_pair.endsWith(suffix)).map(p =>({ + symbol: p.currency_pair.replace(suffix, ""), + price: parseFloat(p.last) + })).keyBy("symbol").mapValues("price").value() + + return allPricesMap } private async fetchSpotBalance(): Promise<{ [k: string]: number }> { diff --git a/src/middlelayers/datafetch/coins/cex/okex.ts b/src/middlelayers/datafetch/coins/cex/okex.ts index 2f0233f..003a257 100644 --- a/src/middlelayers/datafetch/coins/cex/okex.ts +++ b/src/middlelayers/datafetch/coins/cex/okex.ts @@ -1,5 +1,7 @@ import { invoke } from '@tauri-apps/api' import { Exchanger } from './cex' +import { sendHttpRequest } from '../../utils/http' +import _ from 'lodash' export class OkexExchange implements Exchanger { private readonly apiKey: string @@ -36,8 +38,22 @@ export class OkexExchange implements Exchanger { return invoke("query_okex_balance", { apiKey: this.apiKey, apiSecret: this.secret, password: this.password }) } - async fetchCoinsPrice(symbols: string[]): Promise<{ [k: string]: number }> { - return {} + async fetchCoinsPrice(): Promise<{ [k: string]: number }> { + // https://www.okx.com/api/v5/market/tickers?instType=SPOT + const allPrices = await sendHttpRequest<{ + data: { + instId: string + last: string + }[] + }>("GET", "https://www.okx.com/api/v5/market/tickers?instType=SPOT") + + const suffix = "-USDT" + const allPricesMap = _(allPrices.data).filter(p=>p.instId.endsWith(suffix)).map(p =>({ + symbol: p.instId.replace(suffix, ""), + price: parseFloat(p.last) + })).keyBy("symbol").mapValues("price").value() + + return allPricesMap } async verifyConfig(): Promise { diff --git a/src/middlelayers/datafetch/coins/cex/others.ts b/src/middlelayers/datafetch/coins/cex/others.ts index bed7b15..b7e76d0 100644 --- a/src/middlelayers/datafetch/coins/cex/others.ts +++ b/src/middlelayers/datafetch/coins/cex/others.ts @@ -27,7 +27,7 @@ export class OtherCexExchanges implements Exchanger { throw new Error('Method not implemented.') } - async fetchCoinsPrice(symbols: string[]): Promise<{ [k: string]: number }> { + async fetchCoinsPrice(): Promise<{ [k: string]: number }> { throw new Error('Method not implemented.') } diff --git a/src/middlelayers/datafetch/utils/coins.ts b/src/middlelayers/datafetch/utils/coins.ts index 79afdcd..87bfadf 100644 --- a/src/middlelayers/datafetch/utils/coins.ts +++ b/src/middlelayers/datafetch/utils/coins.ts @@ -1,3 +1,4 @@ +import { WalletCoinUSD } from '@/middlelayers/types' import { WalletCoin } from '../types' import _ from 'lodash' @@ -16,7 +17,7 @@ export function combineCoinLists(coinLists: WalletCoin[][]): WalletCoin[] { ).flatten().value() } -export function calculateTotalValue(coinList: WalletCoin[], priceMap: { [k: string]: number }): (Pick & { price: number, usdValue: number })[] { +export function calculateTotalValue(coinList: WalletCoin[], priceMap: { [k: string]: number }): WalletCoinUSD[] { const usdtInUsd = priceMap["USDT"] ?? 1 const getPriceFromWalletCoin = (w: WalletCoin) => { if (!w.price) { diff --git a/src/middlelayers/types.d.ts b/src/middlelayers/types.d.ts index 40bed98..f5b425a 100644 --- a/src/middlelayers/types.d.ts +++ b/src/middlelayers/types.d.ts @@ -162,3 +162,9 @@ export type CurrencyRateDetail = { alias: string symbol: string } + +export type WalletCoinUSD = Pick & { + // price in usd + price: number, + usdValue: number, +}