diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e615161a..84ce8700b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Fixed + +- Improved the account calculations + ## 1.122.0 - 01.03.2022 ### Added diff --git a/apps/api/src/app/account/account.controller.ts b/apps/api/src/app/account/account.controller.ts index b73977f75..64530c377 100644 --- a/apps/api/src/app/account/account.controller.ts +++ b/apps/api/src/app/account/account.controller.ts @@ -101,16 +101,18 @@ export class AccountController { ) { accountsWithAggregations = { ...nullifyValuesInObject(accountsWithAggregations, [ - 'totalBalance', - 'totalValue' + 'totalBalanceInBaseCurrency', + 'totalValueInBaseCurrency' ]), accounts: nullifyValuesInObjects(accountsWithAggregations.accounts, [ 'balance', + 'balanceInBaseCurrency', 'convertedBalance', 'fee', 'quantity', 'unitPrice', - 'value' + 'value', + 'valueInBaseCurrency' ]) }; } diff --git a/apps/api/src/app/account/account.service.ts b/apps/api/src/app/account/account.service.ts index 9f5aab1b2..ec1678131 100644 --- a/apps/api/src/app/account/account.service.ts +++ b/apps/api/src/app/account/account.service.ts @@ -2,6 +2,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { Injectable } from '@nestjs/common'; import { Account, Order, Platform, Prisma } from '@prisma/client'; +import Big from 'big.js'; import { CashDetails } from './interfaces/cash-details.interface'; @@ -105,21 +106,26 @@ export class AccountService { aUserId: string, aCurrency: string ): Promise { - let totalCashBalance = 0; + let totalCashBalanceInBaseCurrency = new Big(0); const accounts = await this.accounts({ where: { userId: aUserId } }); - accounts.forEach((account) => { - totalCashBalance += this.exchangeRateDataService.toCurrency( - account.balance, - account.currency, - aCurrency + for (const account of accounts) { + totalCashBalanceInBaseCurrency = totalCashBalanceInBaseCurrency.plus( + this.exchangeRateDataService.toCurrency( + account.balance, + account.currency, + aCurrency + ) ); - }); + } - return { accounts, balance: totalCashBalance }; + return { + accounts, + balanceInBaseCurrency: totalCashBalanceInBaseCurrency.toNumber() + }; } public async updateAccount( diff --git a/apps/api/src/app/account/interfaces/cash-details.interface.ts b/apps/api/src/app/account/interfaces/cash-details.interface.ts index 146ee6b29..715343766 100644 --- a/apps/api/src/app/account/interfaces/cash-details.interface.ts +++ b/apps/api/src/app/account/interfaces/cash-details.interface.ts @@ -2,5 +2,5 @@ import { Account } from '@prisma/client'; export interface CashDetails { accounts: Account[]; - balance: number; + balanceInBaseCurrency: number; } diff --git a/apps/api/src/app/portfolio/portfolio.service-new.ts b/apps/api/src/app/portfolio/portfolio.service-new.ts index 99e9496fc..d90182c39 100644 --- a/apps/api/src/app/portfolio/portfolio.service-new.ts +++ b/apps/api/src/app/portfolio/portfolio.service-new.ts @@ -100,15 +100,22 @@ export class PortfolioServiceNew { } } + const value = details.accounts[account.id]?.current ?? 0; + const result = { ...account, transactionCount, - convertedBalance: this.exchangeRateDataService.toCurrency( + value, + balanceInBaseCurrency: this.exchangeRateDataService.toCurrency( account.balance, account.currency, userCurrency ), - value: details.accounts[account.id]?.current ?? 0 + valueInBaseCurrency: this.exchangeRateDataService.toCurrency( + value, + account.currency, + userCurrency + ) }; delete result.Order; @@ -119,17 +126,26 @@ export class PortfolioServiceNew { public async getAccountsWithAggregations(aUserId: string): Promise { const accounts = await this.getAccounts(aUserId); - let totalBalance = 0; - let totalValue = 0; + let totalBalanceInBaseCurrency = new Big(0); + let totalValueInBaseCurrency = new Big(0); let transactionCount = 0; for (const account of accounts) { - totalBalance += account.convertedBalance; - totalValue += account.value; + totalBalanceInBaseCurrency = totalBalanceInBaseCurrency.plus( + account.balanceInBaseCurrency + ); + totalValueInBaseCurrency = totalValueInBaseCurrency.plus( + account.valueInBaseCurrency + ); transactionCount += account.transactionCount; } - return { accounts, totalBalance, totalValue, transactionCount }; + return { + accounts, + transactionCount, + totalBalanceInBaseCurrency: totalBalanceInBaseCurrency.toNumber(), + totalValueInBaseCurrency: totalValueInBaseCurrency.toNumber() + }; } public async getInvestments( @@ -293,13 +309,11 @@ export class PortfolioServiceNew { orders: portfolioOrders }); - if (transactionPoints?.length <= 0) { - return { accounts: {}, holdings: {}, hasErrors: false }; - } - portfolioCalculator.setTransactionPoints(transactionPoints); - const portfolioStart = parseDate(transactionPoints[0].date); + const portfolioStart = parseDate( + transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT) + ); const startDate = this.getStartDate(aDateRange, portfolioStart); const currentPositions = await portfolioCalculator.getCurrentPositions( startDate @@ -312,9 +326,11 @@ export class PortfolioServiceNew { const holdings: PortfolioDetails['holdings'] = {}; const totalInvestment = currentPositions.totalInvestment.plus( - cashDetails.balance + cashDetails.balanceInBaseCurrency + ); + const totalValue = currentPositions.currentValue.plus( + cashDetails.balanceInBaseCurrency ); - const totalValue = currentPositions.currentValue.plus(cashDetails.balance); const dataGatheringItems = currentPositions.positions.map((position) => { return { @@ -869,7 +885,7 @@ export class PortfolioServiceNew { const performanceInformation = await this.getPerformance(aImpersonationId); - const { balance } = await this.accountService.getCashDetails( + const { balanceInBaseCurrency } = await this.accountService.getCashDetails( userId, userCurrency ); @@ -887,7 +903,7 @@ export class PortfolioServiceNew { const committedFunds = new Big(totalBuy).minus(totalSell); - const netWorth = new Big(balance) + const netWorth = new Big(balanceInBaseCurrency) .plus(performanceInformation.performance.currentValue) .plus(items) .toNumber(); @@ -917,7 +933,7 @@ export class PortfolioServiceNew { netWorth, totalBuy, totalSell, - cash: balance, + cash: balanceInBaseCurrency, committedFunds: committedFunds.toNumber(), ordersCount: orders.filter((order) => { return order.type === 'BUY' || order.type === 'SELL'; @@ -1153,17 +1169,12 @@ export class PortfolioServiceNew { return accountId === account.id; }); - const convertedBalance = this.exchangeRateDataService.toCurrency( - account.balance, - account.currency, - userCurrency - ); accounts[account.id] = { - balance: convertedBalance, + balance: account.balance, currency: account.currency, - current: convertedBalance, + current: account.balance, name: account.name, - original: convertedBalance + original: account.balance }; for (const order of ordersByAccount) { diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index ca0c25b03..eb1a463ee 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -99,15 +99,22 @@ export class PortfolioService { } } + const value = details.accounts[account.id]?.current ?? 0; + const result = { ...account, transactionCount, - convertedBalance: this.exchangeRateDataService.toCurrency( + value, + balanceInBaseCurrency: this.exchangeRateDataService.toCurrency( account.balance, account.currency, userCurrency ), - value: details.accounts[account.id]?.current ?? 0 + valueInBaseCurrency: this.exchangeRateDataService.toCurrency( + value, + account.currency, + userCurrency + ) }; delete result.Order; @@ -118,17 +125,26 @@ export class PortfolioService { public async getAccountsWithAggregations(aUserId: string): Promise { const accounts = await this.getAccounts(aUserId); - let totalBalance = 0; - let totalValue = 0; + let totalBalanceInBaseCurrency = new Big(0); + let totalValueInBaseCurrency = new Big(0); let transactionCount = 0; for (const account of accounts) { - totalBalance += account.convertedBalance; - totalValue += account.value; + totalBalanceInBaseCurrency = totalBalanceInBaseCurrency.plus( + account.balanceInBaseCurrency + ); + totalValueInBaseCurrency = totalValueInBaseCurrency.plus( + account.valueInBaseCurrency + ); transactionCount += account.transactionCount; } - return { accounts, totalBalance, totalValue, transactionCount }; + return { + accounts, + transactionCount, + totalBalanceInBaseCurrency: totalBalanceInBaseCurrency.toNumber(), + totalValueInBaseCurrency: totalValueInBaseCurrency.toNumber() + }; } public async getInvestments( @@ -281,13 +297,11 @@ export class PortfolioService { userId }); - if (transactionPoints?.length <= 0) { - return { accounts: {}, holdings: {}, hasErrors: false }; - } - portfolioCalculator.setTransactionPoints(transactionPoints); - const portfolioStart = parseDate(transactionPoints[0].date); + const portfolioStart = parseDate( + transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT) + ); const startDate = this.getStartDate(aDateRange, portfolioStart); const currentPositions = await portfolioCalculator.getCurrentPositions( startDate @@ -300,9 +314,11 @@ export class PortfolioService { const holdings: PortfolioDetails['holdings'] = {}; const totalInvestment = currentPositions.totalInvestment.plus( - cashDetails.balance + cashDetails.balanceInBaseCurrency + ); + const totalValue = currentPositions.currentValue.plus( + cashDetails.balanceInBaseCurrency ); - const totalValue = currentPositions.currentValue.plus(cashDetails.balance); const dataGatheringItems = currentPositions.positions.map((position) => { return { @@ -848,7 +864,7 @@ export class PortfolioService { const performanceInformation = await this.getPerformance(aImpersonationId); - const { balance } = await this.accountService.getCashDetails( + const { balanceInBaseCurrency } = await this.accountService.getCashDetails( userId, userCurrency ); @@ -866,7 +882,7 @@ export class PortfolioService { const committedFunds = new Big(totalBuy).minus(totalSell); - const netWorth = new Big(balance) + const netWorth = new Big(balanceInBaseCurrency) .plus(performanceInformation.performance.currentValue) .plus(items) .toNumber(); @@ -882,7 +898,7 @@ export class PortfolioService { totalSell, annualizedPerformancePercent: performanceInformation.performance.annualizedPerformancePercent, - cash: balance, + cash: balanceInBaseCurrency, committedFunds: committedFunds.toNumber(), ordersCount: orders.filter((order) => { return order.type === 'BUY' || order.type === 'SELL'; @@ -1113,17 +1129,12 @@ export class PortfolioService { return accountId === account.id; }); - const convertedBalance = this.exchangeRateDataService.toCurrency( - account.balance, - account.currency, - userCurrency - ); accounts[account.id] = { - balance: convertedBalance, + balance: account.balance, currency: account.currency, - current: convertedBalance, + current: account.balance, name: account.name, - original: convertedBalance + original: account.balance }; for (const order of ordersByAccount) { diff --git a/apps/client/src/app/components/accounts-table/accounts-table.component.html b/apps/client/src/app/components/accounts-table/accounts-table.component.html index f08ea8430..51ad3e58d 100644 --- a/apps/client/src/app/components/accounts-table/accounts-table.component.html +++ b/apps/client/src/app/components/accounts-table/accounts-table.component.html @@ -86,7 +86,7 @@ class="d-inline-block justify-content-end" [isCurrency]="true" [locale]="locale" - [value]="element.convertedBalance" + [value]="element.balance" > @@ -94,7 +94,7 @@ class="d-inline-block justify-content-end" [isCurrency]="true" [locale]="locale" - [value]="totalBalance" + [value]="totalBalanceInBaseCurrency" > @@ -116,7 +116,7 @@ class="d-inline-block justify-content-end" [isCurrency]="true" [locale]="locale" - [value]="totalValue" + [value]="totalValueInBaseCurrency" > diff --git a/apps/client/src/app/components/accounts-table/accounts-table.component.ts b/apps/client/src/app/components/accounts-table/accounts-table.component.ts index 34ddaea6c..994d6ab64 100644 --- a/apps/client/src/app/components/accounts-table/accounts-table.component.ts +++ b/apps/client/src/app/components/accounts-table/accounts-table.component.ts @@ -24,8 +24,8 @@ export class AccountsTableComponent implements OnChanges, OnDestroy, OnInit { @Input() deviceType: string; @Input() locale: string; @Input() showActions: boolean; - @Input() totalBalance: number; - @Input() totalValue: number; + @Input() totalBalanceInBaseCurrency: number; + @Input() totalValueInBaseCurrency: number; @Input() transactionCount: number; @Output() accountDeleted = new EventEmitter(); diff --git a/apps/client/src/app/pages/accounts/accounts-page.component.ts b/apps/client/src/app/pages/accounts/accounts-page.component.ts index 8c3e145aa..60eb1463b 100644 --- a/apps/client/src/app/pages/accounts/accounts-page.component.ts +++ b/apps/client/src/app/pages/accounts/accounts-page.component.ts @@ -28,8 +28,8 @@ export class AccountsPageComponent implements OnDestroy, OnInit { public hasPermissionToCreateAccount: boolean; public hasPermissionToDeleteAccount: boolean; public routeQueryParams: Subscription; - public totalBalance = 0; - public totalValue = 0; + public totalBalanceInBaseCurrency = 0; + public totalValueInBaseCurrency = 0; public transactionCount = 0; public user: User; @@ -106,18 +106,25 @@ export class AccountsPageComponent implements OnDestroy, OnInit { this.dataService .fetchAccounts() .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ accounts, totalBalance, totalValue, transactionCount }) => { - this.accounts = accounts; - this.totalBalance = totalBalance; - this.totalValue = totalValue; - this.transactionCount = transactionCount; - - if (this.accounts?.length <= 0) { - this.router.navigate([], { queryParams: { createDialog: true } }); - } + .subscribe( + ({ + accounts, + totalBalanceInBaseCurrency, + totalValueInBaseCurrency, + transactionCount + }) => { + this.accounts = accounts; + this.totalBalanceInBaseCurrency = totalBalanceInBaseCurrency; + this.totalValueInBaseCurrency = totalValueInBaseCurrency; + this.transactionCount = transactionCount; + + if (this.accounts?.length <= 0) { + this.router.navigate([], { queryParams: { createDialog: true } }); + } - this.changeDetectorRef.markForCheck(); - }); + this.changeDetectorRef.markForCheck(); + } + ); } public onDeleteAccount(aId: string) { diff --git a/apps/client/src/app/pages/accounts/accounts-page.html b/apps/client/src/app/pages/accounts/accounts-page.html index 117a1f5d5..228ccdd78 100644 --- a/apps/client/src/app/pages/accounts/accounts-page.html +++ b/apps/client/src/app/pages/accounts/accounts-page.html @@ -9,8 +9,8 @@ [deviceType]="deviceType" [locale]="user?.settings?.locale" [showActions]="!hasImpersonationId && hasPermissionToDeleteAccount && !user.settings.isRestrictedView" - [totalBalance]="totalBalance" - [totalValue]="totalValue" + [totalBalanceInBaseCurrency]="totalBalanceInBaseCurrency" + [totalValueInBaseCurrency]="totalValueInBaseCurrency" [transactionCount]="transactionCount" (accountDeleted)="onDeleteAccount($event)" (accountToUpdate)="onUpdateAccount($event)" diff --git a/libs/common/src/lib/interfaces/accounts.interface.ts b/libs/common/src/lib/interfaces/accounts.interface.ts index 14732f410..7100a6848 100644 --- a/libs/common/src/lib/interfaces/accounts.interface.ts +++ b/libs/common/src/lib/interfaces/accounts.interface.ts @@ -2,7 +2,7 @@ import { AccountWithValue } from '@ghostfolio/common/types'; export interface Accounts { accounts: AccountWithValue[]; - totalBalance: number; - totalValue: number; + totalBalanceInBaseCurrency: number; + totalValueInBaseCurrency: number; transactionCount: number; } diff --git a/libs/common/src/lib/types/account-with-value.type.ts b/libs/common/src/lib/types/account-with-value.type.ts index a3b81f15b..7c0cca747 100644 --- a/libs/common/src/lib/types/account-with-value.type.ts +++ b/libs/common/src/lib/types/account-with-value.type.ts @@ -1,7 +1,8 @@ import { Account as AccountModel } from '@prisma/client'; export type AccountWithValue = AccountModel & { - convertedBalance: number; + balanceInBaseCurrency: number; transactionCount: number; value: number; + valueInBaseCurrency: number; };