Bugfix/improve account calculations (#737)
* Improve account calculations * Update changelog
This commit is contained in:
parent
63ed227f3f
commit
3de7d3f60e
@ -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
|
||||
|
@ -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'
|
||||
])
|
||||
};
|
||||
}
|
||||
|
@ -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<CashDetails> {
|
||||
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(
|
||||
|
@ -2,5 +2,5 @@ import { Account } from '@prisma/client';
|
||||
|
||||
export interface CashDetails {
|
||||
accounts: Account[];
|
||||
balance: number;
|
||||
balanceInBaseCurrency: number;
|
||||
}
|
||||
|
@ -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<Accounts> {
|
||||
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) {
|
||||
|
@ -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<Accounts> {
|
||||
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) {
|
||||
|
@ -86,7 +86,7 @@
|
||||
class="d-inline-block justify-content-end"
|
||||
[isCurrency]="true"
|
||||
[locale]="locale"
|
||||
[value]="element.convertedBalance"
|
||||
[value]="element.balance"
|
||||
></gf-value>
|
||||
</td>
|
||||
<td *matFooterCellDef class="px-1 text-right" mat-footer-cell>
|
||||
@ -94,7 +94,7 @@
|
||||
class="d-inline-block justify-content-end"
|
||||
[isCurrency]="true"
|
||||
[locale]="locale"
|
||||
[value]="totalBalance"
|
||||
[value]="totalBalanceInBaseCurrency"
|
||||
></gf-value>
|
||||
</td>
|
||||
</ng-container>
|
||||
@ -116,7 +116,7 @@
|
||||
class="d-inline-block justify-content-end"
|
||||
[isCurrency]="true"
|
||||
[locale]="locale"
|
||||
[value]="totalValue"
|
||||
[value]="totalValueInBaseCurrency"
|
||||
></gf-value>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
@ -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<string>();
|
||||
|
@ -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;
|
||||
.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 } });
|
||||
if (this.accounts?.length <= 0) {
|
||||
this.router.navigate([], { queryParams: { createDialog: true } });
|
||||
}
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
}
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
public onDeleteAccount(aId: string) {
|
||||
|
@ -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)"
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user