Extend performance endpoint by net worth per day (#2574)
* Extend performance endpoint by net worth per day * Update changelog
This commit is contained in:
parent
79fc22b5ae
commit
2b491dc732
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Changed
|
||||
|
||||
- Extended the chart in the account detail dialog by historical cash balances
|
||||
- Improved the error log for a timeout in the data source request
|
||||
- Improved the language localization for German (`de`)
|
||||
- Upgraded `angular` from version `16.2.12` to `17.0.4`
|
||||
|
@ -128,8 +128,8 @@ export class AccountController {
|
||||
@Param('id') id: string
|
||||
): Promise<AccountBalancesResponse> {
|
||||
return this.accountBalanceService.getAccountBalances({
|
||||
accountId: id,
|
||||
userId: this.request.user.id
|
||||
filters: [{ id, type: 'ACCOUNT' }],
|
||||
user: this.request.user
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -346,16 +346,34 @@ export class PortfolioController {
|
||||
this.userService.isRestrictedView(this.request.user)
|
||||
) {
|
||||
performanceInformation.chart = performanceInformation.chart.map(
|
||||
({ date, netPerformanceInPercentage, totalInvestment, value }) => {
|
||||
({
|
||||
date,
|
||||
netPerformanceInPercentage,
|
||||
netWorth,
|
||||
totalInvestment,
|
||||
value
|
||||
}) => {
|
||||
return {
|
||||
date,
|
||||
netPerformanceInPercentage,
|
||||
totalInvestment: new Big(totalInvestment)
|
||||
.div(performanceInformation.performance.totalInvestment)
|
||||
.toNumber(),
|
||||
valueInPercentage: new Big(value)
|
||||
.div(performanceInformation.performance.currentValue)
|
||||
.toNumber()
|
||||
netWorthInPercentage:
|
||||
performanceInformation.performance.currentNetWorth === 0
|
||||
? 0
|
||||
: new Big(netWorth)
|
||||
.div(performanceInformation.performance.currentNetWorth)
|
||||
.toNumber(),
|
||||
totalInvestment:
|
||||
performanceInformation.performance.totalInvestment === 0
|
||||
? 0
|
||||
: new Big(totalInvestment)
|
||||
.div(performanceInformation.performance.totalInvestment)
|
||||
.toNumber(),
|
||||
valueInPercentage:
|
||||
performanceInformation.performance.currentValue === 0
|
||||
? 0
|
||||
: new Big(value)
|
||||
.div(performanceInformation.performance.currentValue)
|
||||
.toNumber()
|
||||
};
|
||||
}
|
||||
);
|
||||
@ -365,6 +383,7 @@ export class PortfolioController {
|
||||
[
|
||||
'currentGrossPerformance',
|
||||
'currentNetPerformance',
|
||||
'currentNetWorth',
|
||||
'currentValue',
|
||||
'totalInvestment'
|
||||
]
|
||||
|
@ -12,6 +12,7 @@ import { CurrencyClusterRiskBaseCurrencyCurrentInvestment } from '@ghostfolio/ap
|
||||
import { CurrencyClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/current-investment';
|
||||
import { EmergencyFundSetup } from '@ghostfolio/api/models/rules/emergency-fund/emergency-fund-setup';
|
||||
import { FeeRatioInitialInvestment } from '@ghostfolio/api/models/rules/fees/fee-ratio-initial-investment';
|
||||
import { AccountBalanceService } from '@ghostfolio/api/services/account-balance/account-balance.service';
|
||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
|
||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
|
||||
@ -67,14 +68,16 @@ import {
|
||||
isBefore,
|
||||
isSameMonth,
|
||||
isSameYear,
|
||||
isValid,
|
||||
max,
|
||||
min,
|
||||
parseISO,
|
||||
set,
|
||||
setDayOfYear,
|
||||
subDays,
|
||||
subYears
|
||||
} from 'date-fns';
|
||||
import { isEmpty, sortBy, uniq, uniqBy } from 'lodash';
|
||||
import { isEmpty, last, sortBy, uniq, uniqBy } from 'lodash';
|
||||
|
||||
import {
|
||||
HistoricalDataContainer,
|
||||
@ -91,6 +94,7 @@ const europeMarkets = require('../../assets/countries/europe-markets.json');
|
||||
@Injectable()
|
||||
export class PortfolioService {
|
||||
public constructor(
|
||||
private readonly accountBalanceService: AccountBalanceService,
|
||||
private readonly accountService: AccountService,
|
||||
private readonly currentRateService: CurrentRateService,
|
||||
private readonly dataProviderService: DataProviderService,
|
||||
@ -114,8 +118,12 @@ export class PortfolioService {
|
||||
}): Promise<AccountWithValue[]> {
|
||||
const where: Prisma.AccountWhereInput = { userId: userId };
|
||||
|
||||
if (filters?.[0].id && filters?.[0].type === 'ACCOUNT') {
|
||||
where.id = filters[0].id;
|
||||
const accountFilter = filters?.find(({ type }) => {
|
||||
return type === 'ACCOUNT';
|
||||
});
|
||||
|
||||
if (accountFilter) {
|
||||
where.id = accountFilter.id;
|
||||
}
|
||||
|
||||
const [accounts, details] = await Promise.all([
|
||||
@ -267,6 +275,13 @@ export class PortfolioService {
|
||||
includeDrafts: true
|
||||
});
|
||||
|
||||
if (transactionPoints.length === 0) {
|
||||
return {
|
||||
investments: [],
|
||||
streaks: { currentStreak: 0, longestStreak: 0 }
|
||||
};
|
||||
}
|
||||
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
currency: this.request.user.Settings.settings.baseCurrency,
|
||||
currentRateService: this.currentRateService,
|
||||
@ -274,12 +289,6 @@ export class PortfolioService {
|
||||
});
|
||||
|
||||
portfolioCalculator.setTransactionPoints(transactionPoints);
|
||||
if (transactionPoints.length === 0) {
|
||||
return {
|
||||
investments: [],
|
||||
streaks: { currentStreak: 0, longestStreak: 0 }
|
||||
};
|
||||
}
|
||||
|
||||
let investments: InvestmentItem[];
|
||||
|
||||
@ -367,67 +376,6 @@ export class PortfolioService {
|
||||
};
|
||||
}
|
||||
|
||||
public async getChart({
|
||||
dateRange = 'max',
|
||||
filters,
|
||||
impersonationId,
|
||||
userCurrency,
|
||||
userId,
|
||||
withExcludedAccounts = false
|
||||
}: {
|
||||
dateRange?: DateRange;
|
||||
filters?: Filter[];
|
||||
impersonationId: string;
|
||||
userCurrency: string;
|
||||
userId: string;
|
||||
withExcludedAccounts?: boolean;
|
||||
}): Promise<HistoricalDataContainer> {
|
||||
userId = await this.getUserId(impersonationId, userId);
|
||||
|
||||
const { portfolioOrders, transactionPoints } =
|
||||
await this.getTransactionPoints({
|
||||
filters,
|
||||
userId,
|
||||
withExcludedAccounts
|
||||
});
|
||||
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
currency: userCurrency,
|
||||
currentRateService: this.currentRateService,
|
||||
orders: portfolioOrders
|
||||
});
|
||||
|
||||
portfolioCalculator.setTransactionPoints(transactionPoints);
|
||||
if (transactionPoints.length === 0) {
|
||||
return {
|
||||
isAllTimeHigh: false,
|
||||
isAllTimeLow: false,
|
||||
items: []
|
||||
};
|
||||
}
|
||||
const endDate = new Date();
|
||||
|
||||
const portfolioStart = parseDate(transactionPoints[0].date);
|
||||
const startDate = this.getStartDate(dateRange, portfolioStart);
|
||||
|
||||
const daysInMarket = differenceInDays(new Date(), startDate);
|
||||
const step = Math.round(
|
||||
daysInMarket / Math.min(daysInMarket, MAX_CHART_ITEMS)
|
||||
);
|
||||
|
||||
const items = await portfolioCalculator.getChartData(
|
||||
startDate,
|
||||
endDate,
|
||||
step
|
||||
);
|
||||
|
||||
return {
|
||||
items,
|
||||
isAllTimeHigh: false,
|
||||
isAllTimeLow: false
|
||||
};
|
||||
}
|
||||
|
||||
public async getDetails({
|
||||
dateRange = 'max',
|
||||
filters,
|
||||
@ -1028,12 +976,6 @@ export class PortfolioService {
|
||||
userId
|
||||
});
|
||||
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
currency: this.request.user.Settings.settings.baseCurrency,
|
||||
currentRateService: this.currentRateService,
|
||||
orders: portfolioOrders
|
||||
});
|
||||
|
||||
if (transactionPoints?.length <= 0) {
|
||||
return {
|
||||
hasErrors: false,
|
||||
@ -1041,6 +983,12 @@ export class PortfolioService {
|
||||
};
|
||||
}
|
||||
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
currency: this.request.user.Settings.settings.baseCurrency,
|
||||
currentRateService: this.currentRateService,
|
||||
orders: portfolioOrders
|
||||
});
|
||||
|
||||
portfolioCalculator.setTransactionPoints(transactionPoints);
|
||||
|
||||
const portfolioStart = parseDate(transactionPoints[0].date);
|
||||
@ -1126,6 +1074,31 @@ export class PortfolioService {
|
||||
const user = await this.userService.user({ id: userId });
|
||||
const userCurrency = this.getUserCurrency(user);
|
||||
|
||||
const accountBalances = await this.accountBalanceService.getAccountBalances(
|
||||
{ filters, user }
|
||||
);
|
||||
|
||||
let accountBalanceItems: HistoricalDataItem[] = Object.values(
|
||||
// Reduce the array to a map with unique dates as keys
|
||||
accountBalances.balances.reduce(
|
||||
(
|
||||
map: { [date: string]: HistoricalDataItem },
|
||||
{ date, valueInBaseCurrency }
|
||||
) => {
|
||||
const formattedDate = format(date, DATE_FORMAT);
|
||||
|
||||
// Store the item in the map, overwriting if the date already exists
|
||||
map[formattedDate] = {
|
||||
date: formattedDate,
|
||||
value: valueInBaseCurrency
|
||||
};
|
||||
|
||||
return map;
|
||||
},
|
||||
{}
|
||||
)
|
||||
);
|
||||
|
||||
const { portfolioOrders, transactionPoints } =
|
||||
await this.getTransactionPoints({
|
||||
filters,
|
||||
@ -1139,7 +1112,7 @@ export class PortfolioService {
|
||||
orders: portfolioOrders
|
||||
});
|
||||
|
||||
if (transactionPoints?.length <= 0) {
|
||||
if (accountBalanceItems?.length <= 0 && transactionPoints?.length <= 0) {
|
||||
return {
|
||||
chart: [],
|
||||
firstOrderDate: undefined,
|
||||
@ -1149,6 +1122,7 @@ export class PortfolioService {
|
||||
currentGrossPerformancePercent: 0,
|
||||
currentNetPerformance: 0,
|
||||
currentNetPerformancePercent: 0,
|
||||
currentNetWorth: 0,
|
||||
currentValue: 0,
|
||||
totalInvestment: 0
|
||||
}
|
||||
@ -1157,7 +1131,15 @@ export class PortfolioService {
|
||||
|
||||
portfolioCalculator.setTransactionPoints(transactionPoints);
|
||||
|
||||
const portfolioStart = parseDate(transactionPoints[0].date);
|
||||
const portfolioStart = min(
|
||||
[
|
||||
parseDate(accountBalanceItems[0]?.date),
|
||||
parseDate(transactionPoints[0]?.date)
|
||||
].filter((date) => {
|
||||
return isValid(date);
|
||||
})
|
||||
);
|
||||
|
||||
const startDate = this.getStartDate(dateRange, portfolioStart);
|
||||
const {
|
||||
currentValue,
|
||||
@ -1175,17 +1157,17 @@ export class PortfolioService {
|
||||
let currentNetPerformance = netPerformance;
|
||||
let currentNetPerformancePercent = netPerformancePercentage;
|
||||
|
||||
const historicalDataContainer = await this.getChart({
|
||||
const { items } = await this.getChart({
|
||||
dateRange,
|
||||
filters,
|
||||
impersonationId,
|
||||
portfolioOrders,
|
||||
transactionPoints,
|
||||
userCurrency,
|
||||
userId,
|
||||
withExcludedAccounts
|
||||
userId
|
||||
});
|
||||
|
||||
const itemOfToday = historicalDataContainer.items.find((item) => {
|
||||
return item.date === format(new Date(), DATE_FORMAT);
|
||||
const itemOfToday = items.find(({ date }) => {
|
||||
return date === format(new Date(), DATE_FORMAT);
|
||||
});
|
||||
|
||||
if (itemOfToday) {
|
||||
@ -1195,34 +1177,42 @@ export class PortfolioService {
|
||||
).div(100);
|
||||
}
|
||||
|
||||
accountBalanceItems = accountBalanceItems.filter(({ date }) => {
|
||||
return !isBefore(parseDate(date), startDate);
|
||||
});
|
||||
|
||||
const accountBalanceItemOfToday = accountBalanceItems.find(({ date }) => {
|
||||
return date === format(new Date(), DATE_FORMAT);
|
||||
});
|
||||
|
||||
if (!accountBalanceItemOfToday) {
|
||||
accountBalanceItems.push({
|
||||
date: format(new Date(), DATE_FORMAT),
|
||||
value: last(accountBalanceItems)?.value ?? 0
|
||||
});
|
||||
}
|
||||
|
||||
const mergedHistoricalDataItems = this.mergeHistoricalDataItems(
|
||||
accountBalanceItems,
|
||||
items
|
||||
);
|
||||
|
||||
const currentHistoricalDataItem = last(mergedHistoricalDataItems);
|
||||
const currentNetWorth = currentHistoricalDataItem?.netWorth ?? 0;
|
||||
|
||||
return {
|
||||
errors,
|
||||
hasErrors,
|
||||
chart: historicalDataContainer.items.map(
|
||||
({
|
||||
date,
|
||||
netPerformance: netPerformanceOfItem,
|
||||
netPerformanceInPercentage,
|
||||
totalInvestment: totalInvestmentOfItem,
|
||||
value
|
||||
}) => {
|
||||
return {
|
||||
date,
|
||||
netPerformanceInPercentage,
|
||||
value,
|
||||
netPerformance: netPerformanceOfItem,
|
||||
totalInvestment: totalInvestmentOfItem
|
||||
};
|
||||
}
|
||||
),
|
||||
firstOrderDate: parseDate(historicalDataContainer.items[0]?.date),
|
||||
chart: mergedHistoricalDataItems,
|
||||
firstOrderDate: parseDate(items[0]?.date),
|
||||
performance: {
|
||||
currentValue: currentValue.toNumber(),
|
||||
currentNetWorth,
|
||||
currentGrossPerformance: currentGrossPerformance.toNumber(),
|
||||
currentGrossPerformancePercent:
|
||||
currentGrossPerformancePercent.toNumber(),
|
||||
currentNetPerformance: currentNetPerformance.toNumber(),
|
||||
currentNetPerformancePercent: currentNetPerformancePercent.toNumber(),
|
||||
currentValue: currentValue.toNumber(),
|
||||
totalInvestment: totalInvestment.toNumber()
|
||||
}
|
||||
};
|
||||
@ -1376,6 +1366,62 @@ export class PortfolioService {
|
||||
return cashPositions;
|
||||
}
|
||||
|
||||
private async getChart({
|
||||
dateRange = 'max',
|
||||
impersonationId,
|
||||
portfolioOrders,
|
||||
transactionPoints,
|
||||
userCurrency,
|
||||
userId
|
||||
}: {
|
||||
dateRange?: DateRange;
|
||||
impersonationId: string;
|
||||
portfolioOrders: PortfolioOrder[];
|
||||
transactionPoints: TransactionPoint[];
|
||||
userCurrency: string;
|
||||
userId: string;
|
||||
}): Promise<HistoricalDataContainer> {
|
||||
if (transactionPoints.length === 0) {
|
||||
return {
|
||||
isAllTimeHigh: false,
|
||||
isAllTimeLow: false,
|
||||
items: []
|
||||
};
|
||||
}
|
||||
|
||||
userId = await this.getUserId(impersonationId, userId);
|
||||
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
currency: userCurrency,
|
||||
currentRateService: this.currentRateService,
|
||||
orders: portfolioOrders
|
||||
});
|
||||
|
||||
portfolioCalculator.setTransactionPoints(transactionPoints);
|
||||
|
||||
const endDate = new Date();
|
||||
|
||||
const portfolioStart = parseDate(transactionPoints[0].date);
|
||||
const startDate = this.getStartDate(dateRange, portfolioStart);
|
||||
|
||||
const daysInMarket = differenceInDays(new Date(), startDate);
|
||||
const step = Math.round(
|
||||
daysInMarket / Math.min(daysInMarket, MAX_CHART_ITEMS)
|
||||
);
|
||||
|
||||
const items = await portfolioCalculator.getChartData(
|
||||
startDate,
|
||||
endDate,
|
||||
step
|
||||
);
|
||||
|
||||
return {
|
||||
items,
|
||||
isAllTimeHigh: false,
|
||||
isAllTimeLow: false
|
||||
};
|
||||
}
|
||||
|
||||
private getDividendsByGroup({
|
||||
dividends,
|
||||
groupBy
|
||||
@ -1999,4 +2045,44 @@ export class PortfolioService {
|
||||
|
||||
return { accounts, platforms };
|
||||
}
|
||||
|
||||
private mergeHistoricalDataItems(
|
||||
accountBalanceItems: HistoricalDataItem[],
|
||||
performanceChartItems: HistoricalDataItem[]
|
||||
): HistoricalDataItem[] {
|
||||
const historicalDataItemsMap: { [date: string]: HistoricalDataItem } = {};
|
||||
let latestAccountBalance = 0;
|
||||
|
||||
for (const item of accountBalanceItems.concat(performanceChartItems)) {
|
||||
const isAccountBalanceItem = accountBalanceItems.includes(item);
|
||||
|
||||
const totalAccountBalance = isAccountBalanceItem
|
||||
? item.value
|
||||
: latestAccountBalance;
|
||||
|
||||
if (isAccountBalanceItem && performanceChartItems.length > 0) {
|
||||
latestAccountBalance = item.value;
|
||||
} else {
|
||||
historicalDataItemsMap[item.date] = {
|
||||
...item,
|
||||
totalAccountBalance,
|
||||
netWorth:
|
||||
(isAccountBalanceItem ? 0 : item.value) + totalAccountBalance
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to an array and sort by date in ascending order
|
||||
const historicalDataItems = Object.keys(historicalDataItemsMap).map(
|
||||
(date) => {
|
||||
return historicalDataItemsMap[date];
|
||||
}
|
||||
);
|
||||
|
||||
historicalDataItems.sort(
|
||||
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
|
||||
);
|
||||
|
||||
return historicalDataItems;
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { AccountBalanceService } from '@ghostfolio/api/services/account-balance/account-balance.service';
|
||||
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
|
||||
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
@Module({
|
||||
exports: [AccountBalanceService],
|
||||
imports: [PrismaModule],
|
||||
imports: [ExchangeRateDataModule, PrismaModule],
|
||||
providers: [AccountBalanceService]
|
||||
})
|
||||
export class AccountBalanceModule {}
|
||||
|
@ -1,11 +1,16 @@
|
||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
||||
import { AccountBalancesResponse } from '@ghostfolio/common/interfaces';
|
||||
import { AccountBalancesResponse, Filter } from '@ghostfolio/common/interfaces';
|
||||
import { UserWithSettings } from '@ghostfolio/common/types';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AccountBalance, Prisma } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class AccountBalanceService {
|
||||
public constructor(private readonly prismaService: PrismaService) {}
|
||||
public constructor(
|
||||
private readonly exchangeRateDataService: ExchangeRateDataService,
|
||||
private readonly prismaService: PrismaService
|
||||
) {}
|
||||
|
||||
public async createAccountBalance(
|
||||
data: Prisma.AccountBalanceCreateInput
|
||||
@ -16,27 +21,46 @@ export class AccountBalanceService {
|
||||
}
|
||||
|
||||
public async getAccountBalances({
|
||||
accountId,
|
||||
userId
|
||||
filters,
|
||||
user
|
||||
}: {
|
||||
accountId: string;
|
||||
userId: string;
|
||||
filters?: Filter[];
|
||||
user: UserWithSettings;
|
||||
}): Promise<AccountBalancesResponse> {
|
||||
const where: Prisma.AccountBalanceWhereInput = { userId: user.id };
|
||||
|
||||
const accountFilter = filters?.find(({ type }) => {
|
||||
return type === 'ACCOUNT';
|
||||
});
|
||||
|
||||
if (accountFilter) {
|
||||
where.accountId = accountFilter.id;
|
||||
}
|
||||
|
||||
const balances = await this.prismaService.accountBalance.findMany({
|
||||
where,
|
||||
orderBy: {
|
||||
date: 'asc'
|
||||
},
|
||||
select: {
|
||||
Account: true,
|
||||
date: true,
|
||||
id: true,
|
||||
value: true
|
||||
},
|
||||
where: {
|
||||
accountId,
|
||||
userId
|
||||
}
|
||||
});
|
||||
|
||||
return { balances };
|
||||
return {
|
||||
balances: balances.map((balance) => {
|
||||
return {
|
||||
...balance,
|
||||
valueInBaseCurrency: this.exchangeRateDataService.toCurrency(
|
||||
balance.value,
|
||||
balance.Account.currency,
|
||||
user.Settings.settings.baseCurrency
|
||||
)
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -122,13 +122,13 @@ export class AccountDetailDialog implements OnDestroy, OnInit {
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(({ chart }) => {
|
||||
this.historicalDataItems = chart.map(
|
||||
({ date, value, valueInPercentage }) => {
|
||||
({ date, netWorth, netWorthInPercentage }) => {
|
||||
return {
|
||||
date,
|
||||
value:
|
||||
this.hasImpersonationId || this.user.settings.isRestrictedView
|
||||
? valueInPercentage
|
||||
: value
|
||||
? netWorthInPercentage
|
||||
: netWorth
|
||||
};
|
||||
}
|
||||
);
|
||||
|
@ -5,7 +5,10 @@ export interface HistoricalDataItem {
|
||||
marketPrice?: number;
|
||||
netPerformance?: number;
|
||||
netPerformanceInPercentage?: number;
|
||||
netWorth?: number;
|
||||
netWorthInPercentage?: number;
|
||||
quantity?: number;
|
||||
totalAccountBalance?: number;
|
||||
totalInvestment?: number;
|
||||
value?: number;
|
||||
valueInPercentage?: number;
|
||||
|
@ -4,6 +4,7 @@ export interface PortfolioPerformance {
|
||||
currentGrossPerformancePercent: number;
|
||||
currentNetPerformance: number;
|
||||
currentNetPerformancePercent: number;
|
||||
currentNetWorth: number;
|
||||
currentValue: number;
|
||||
totalInvestment: number;
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { AccountBalance } from '@prisma/client';
|
||||
|
||||
export interface AccountBalancesResponse {
|
||||
balances: Pick<AccountBalance, 'date' | 'id' | 'value'>[];
|
||||
balances: (Pick<AccountBalance, 'date' | 'id' | 'value'> & {
|
||||
valueInBaseCurrency: number;
|
||||
})[];
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user