From 0a85a56c67224feac3ba72f01b5912a8bb8efc79 Mon Sep 17 00:00:00 2001 From: Thomas <4159106+dtslvr@users.noreply.github.com> Date: Mon, 9 Aug 2021 21:26:41 +0200 Subject: [PATCH] Respect cash balance in allocations, do not hide cryptocurrency holdings (#280) * Respect cash balance in allocations, do not hide cryptocurrency holdings * Update changelog --- CHANGELOG.md | 5 ++ .../src/app/portfolio/portfolio.service.ts | 72 +++++++++++++++++-- .../positions/positions.component.ts | 7 -- 3 files changed, 71 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7368c2f0..778fe6bfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Improved the data gathering handling on server restart +- Respected the cash balance on the allocations page + +### Fixed + +- Fixed hidden cryptocurrency holdings ## 1.35.0 - 08.08.2021 diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 1e954d71f..d95331ca7 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -1,4 +1,5 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service'; +import { CashDetails } from '@ghostfolio/api/app/account/interfaces/cash-details.interface'; import { CurrentRateService } from '@ghostfolio/api/app/core/current-rate.service'; import { PortfolioOrder } from '@ghostfolio/api/app/core/interfaces/portfolio-order.interface'; import { TimelineSpecification } from '@ghostfolio/api/app/core/interfaces/timeline-specification.interface'; @@ -17,10 +18,11 @@ import { FeeRatioInitialInvestment } from '@ghostfolio/api/models/rules/fees/fee import { DataProviderService } from '@ghostfolio/api/services/data-provider.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service'; +import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces'; import { EnhancedSymbolProfile } from '@ghostfolio/api/services/interfaces/symbol-profile.interface'; import { RulesService } from '@ghostfolio/api/services/rules.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service'; -import { UNKNOWN_KEY } from '@ghostfolio/common/config'; +import { UNKNOWN_KEY, ghostfolioCashSymbol } from '@ghostfolio/common/config'; import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper'; import { PortfolioOverview, @@ -38,7 +40,12 @@ import { } from '@ghostfolio/common/types'; import { Inject, Injectable } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; -import { Currency, DataSource, Type as TypeOfOrder } from '@prisma/client'; +import { + AssetClass, + Currency, + DataSource, + Type as TypeOfOrder +} from '@prisma/client'; import Big from 'big.js'; import { endOfToday, @@ -201,8 +208,16 @@ export class PortfolioService { throw new Error('Missing information'); } + const cashDetails = await this.accountService.getCashDetails( + userId, + userCurrency + ); + const result: { [symbol: string]: PortfolioPosition } = {}; - const totalValue = currentPositions.currentValue; + const totalInvestment = currentPositions.totalInvestment.plus( + cashDetails.balance + ); + const totalValue = currentPositions.currentValue.plus(cashDetails.balance); const symbols = currentPositions.positions.map( (position) => position.symbol @@ -231,9 +246,7 @@ export class PortfolioService { result[item.symbol] = { accounts, allocationCurrent: value.div(totalValue).toNumber(), - allocationInvestment: item.investment - .div(currentPositions.totalInvestment) - .toNumber(), + allocationInvestment: item.investment.div(totalInvestment).toNumber(), assetClass: symbolProfile.assetClass, countries: symbolProfile.countries, currency: item.currency, @@ -252,6 +265,13 @@ export class PortfolioService { }; } + // TODO: Add a cash position for each currency + result[ghostfolioCashSymbol] = await this.getCashPosition({ + cashDetails, + investment: totalInvestment, + value: totalValue + }); + return result; } @@ -660,6 +680,46 @@ export class PortfolioService { }; } + private async getCashPosition({ + cashDetails, + investment, + value + }: { + cashDetails: CashDetails; + investment: Big; + value: Big; + }) { + const accounts = {}; + const cashValue = new Big(cashDetails.balance); + + cashDetails.accounts.forEach((account) => { + accounts[account.name] = { + current: account.balance, + original: account.balance + }; + }); + + return { + accounts, + allocationCurrent: cashValue.div(value).toNumber(), + allocationInvestment: cashValue.div(investment).toNumber(), + assetClass: AssetClass.CASH, + countries: [], + currency: Currency.CHF, + grossPerformance: 0, + grossPerformancePercent: 0, + investment: cashValue.toNumber(), + marketPrice: 0, + marketState: MarketState.open, + name: 'Cash', + quantity: 0, + sectors: [], + symbol: ghostfolioCashSymbol, + transactionCount: 0, + value: cashValue.toNumber() + }; + } + private getStartDate(aDateRange: DateRange, portfolioStart: Date) { switch (aDateRange) { case '1d': diff --git a/apps/client/src/app/components/positions/positions.component.ts b/apps/client/src/app/components/positions/positions.component.ts index b80700a77..bd0540eff 100644 --- a/apps/client/src/app/components/positions/positions.component.ts +++ b/apps/client/src/app/components/positions/positions.component.ts @@ -7,7 +7,6 @@ import { } from '@angular/core'; import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces'; import { Position } from '@ghostfolio/common/interfaces'; -import { AssetClass } from '@prisma/client'; @Component({ selector: 'gf-positions', @@ -26,8 +25,6 @@ export class PositionsComponent implements OnChanges, OnInit { public positionsRest: Position[] = []; public positionsWithPriority: Position[] = []; - private ignoreAssetClasses = [AssetClass.CASH.toString()]; - public constructor() {} public ngOnInit() {} @@ -44,10 +41,6 @@ export class PositionsComponent implements OnChanges, OnInit { this.positionsWithPriority = []; for (const portfolioPosition of this.positions) { - if (this.ignoreAssetClasses.includes(portfolioPosition.assetClass)) { - continue; - } - if ( portfolioPosition.marketState === MarketState.open || this.range !== '1d'