diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index ce0e736ee..7ddf22bb0 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -5,8 +5,8 @@ import { } from '@ghostfolio/api/helper/object.helper'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; import { + PortfolioDetails, PortfolioPerformance, - PortfolioPosition, PortfolioReport, PortfolioSummary } from '@ghostfolio/common/interfaces'; @@ -124,13 +124,11 @@ export class PortfolioController { @Headers('impersonation-id') impersonationId, @Query('range') range, @Res() res: Response - ): Promise<{ [symbol: string]: PortfolioPosition }> { - const { details, hasErrors } = await this.portfolioService.getDetails( - impersonationId, - range - ); + ): Promise { + const { accounts, holdings, hasErrors } = + await this.portfolioService.getDetails(impersonationId, range); - if (hasErrors || hasNotDefinedValuesInObject(details)) { + if (hasErrors || hasNotDefinedValuesInObject(holdings)) { res.status(StatusCodes.ACCEPTED); } @@ -138,13 +136,13 @@ export class PortfolioController { impersonationId || this.userService.isRestrictedView(this.request.user) ) { - const totalInvestment = Object.values(details) + const totalInvestment = Object.values(holdings) .map((portfolioPosition) => { return portfolioPosition.investment; }) .reduce((a, b) => a + b, 0); - const totalValue = Object.values(details) + const totalValue = Object.values(holdings) .map((portfolioPosition) => { return this.exchangeRateDataService.toCurrency( portfolioPosition.quantity * portfolioPosition.marketPrice, @@ -154,7 +152,7 @@ export class PortfolioController { }) .reduce((a, b) => a + b, 0); - for (const [symbol, portfolioPosition] of Object.entries(details)) { + for (const [symbol, portfolioPosition] of Object.entries(holdings)) { portfolioPosition.grossPerformance = null; portfolioPosition.investment = portfolioPosition.investment / totalInvestment; @@ -171,7 +169,7 @@ export class PortfolioController { } } - return res.json(details); + return res.json({ accounts, holdings }); } @Get('performance') diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index a3f76b804..8f03e9e97 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -24,6 +24,7 @@ import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.se import { UNKNOWN_KEY, ghostfolioCashSymbol } from '@ghostfolio/common/config'; import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper'; import { + PortfolioDetails, PortfolioPerformance, PortfolioPosition, PortfolioReport, @@ -154,10 +155,7 @@ export class PortfolioService { public async getDetails( aImpersonationId: string, aDateRange: DateRange = 'max' - ): Promise<{ - details: { [symbol: string]: PortfolioPosition }; - hasErrors: boolean; - }> { + ): Promise { const userId = await this.getUserId(aImpersonationId); const userCurrency = this.request.user.Settings.currency; @@ -171,7 +169,7 @@ export class PortfolioService { }); if (transactionPoints?.length <= 0) { - return { details: {}, hasErrors: false }; + return { accounts: {}, holdings: {}, hasErrors: false }; } portfolioCalculator.setTransactionPoints(transactionPoints); @@ -187,7 +185,7 @@ export class PortfolioService { userCurrency ); - const details: { [symbol: string]: PortfolioPosition } = {}; + const holdings: PortfolioDetails['holdings'] = {}; const totalInvestment = currentPositions.totalInvestment.plus( cashDetails.balance ); @@ -217,7 +215,7 @@ export class PortfolioService { const value = item.quantity.mul(item.marketPrice); const symbolProfile = symbolProfileMap[item.symbol]; const dataProviderResponse = dataProviderResponses[item.symbol]; - details[item.symbol] = { + holdings[item.symbol] = { accounts, allocationCurrent: value.div(totalValue).toNumber(), allocationInvestment: item.investment.div(totalInvestment).toNumber(), @@ -241,13 +239,13 @@ export class PortfolioService { } // TODO: Add a cash position for each currency - details[ghostfolioCashSymbol] = await this.getCashPosition({ + holdings[ghostfolioCashSymbol] = await this.getCashPosition({ cashDetails, investment: totalInvestment, value: totalValue }); - return { details, hasErrors: currentPositions.hasErrors }; + return { accounts, holdings, hasErrors: currentPositions.hasErrors }; } public async getPosition( diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts index 123e073fa..9d0ffbf22 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts @@ -4,7 +4,11 @@ import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UNKNOWN_KEY } from '@ghostfolio/common/config'; -import { PortfolioPosition, User } from '@ghostfolio/common/interfaces'; +import { + PortfolioDetails, + PortfolioPosition, + User +} from '@ghostfolio/common/interfaces'; import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -31,7 +35,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { { label: 'Initial', value: 'original' }, { label: 'Current', value: 'current' } ]; - public portfolioPositions: { [symbol: string]: PortfolioPosition }; + public portfolioDetails: PortfolioDetails; public positions: { [symbol: string]: any }; public positionsArray: PortfolioPosition[]; public sectors: { @@ -66,11 +70,12 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { }); this.dataService - .fetchPortfolioPositions({}) + .fetchPortfolioDetails({}) .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe((response = {}) => { - this.portfolioPositions = response; - this.initializeAnalysisData(this.portfolioPositions, this.period); + .subscribe((portfolioDetails) => { + this.portfolioDetails = portfolioDetails; + + this.initializeAnalysisData(this.period); this.changeDetectorRef.markForCheck(); }); @@ -86,12 +91,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { }); } - public initializeAnalysisData( - aPortfolioPositions: { - [symbol: string]: PortfolioPosition; - }, - aPeriod: string - ) { + public initializeAnalysisData(aPeriod: string) { this.accounts = {}; this.continents = { [UNKNOWN_KEY]: { @@ -114,7 +114,18 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { } }; - for (const [symbol, position] of Object.entries(aPortfolioPositions)) { + for (const [name, { current, original }] of Object.entries( + this.portfolioDetails.accounts + )) { + this.accounts[name] = { + name, + value: aPeriod === 'original' ? original : current + }; + } + + for (const [symbol, position] of Object.entries( + this.portfolioDetails.holdings + )) { this.positions[symbol] = { assetClass: position.assetClass, currency: position.currency, @@ -126,20 +137,6 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { }; this.positionsArray.push(position); - for (const [account, { current, original }] of Object.entries( - position.accounts - )) { - if (this.accounts[account]?.value) { - this.accounts[account].value += - aPeriod === 'original' ? original : current; - } else { - this.accounts[account] = { - name: account, - value: aPeriod === 'original' ? original : current - }; - } - } - if (position.countries.length > 0) { for (const country of position.countries) { const { code, continent, name, weight } = country; @@ -152,8 +149,8 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { value: weight * (aPeriod === 'original' - ? this.portfolioPositions[symbol].investment - : this.portfolioPositions[symbol].value) + ? this.portfolioDetails.holdings[symbol].investment + : this.portfolioDetails.holdings[symbol].value) }; } @@ -165,21 +162,21 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { value: weight * (aPeriod === 'original' - ? this.portfolioPositions[symbol].investment - : this.portfolioPositions[symbol].value) + ? this.portfolioDetails.holdings[symbol].investment + : this.portfolioDetails.holdings[symbol].value) }; } } } else { this.continents[UNKNOWN_KEY].value += aPeriod === 'original' - ? this.portfolioPositions[symbol].investment - : this.portfolioPositions[symbol].value; + ? this.portfolioDetails.holdings[symbol].investment + : this.portfolioDetails.holdings[symbol].value; this.countries[UNKNOWN_KEY].value += aPeriod === 'original' - ? this.portfolioPositions[symbol].investment - : this.portfolioPositions[symbol].value; + ? this.portfolioDetails.holdings[symbol].investment + : this.portfolioDetails.holdings[symbol].value; } if (position.sectors.length > 0) { @@ -194,16 +191,16 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { value: weight * (aPeriod === 'original' - ? this.portfolioPositions[symbol].investment - : this.portfolioPositions[symbol].value) + ? this.portfolioDetails.holdings[symbol].investment + : this.portfolioDetails.holdings[symbol].value) }; } } } else { this.sectors[UNKNOWN_KEY].value += aPeriod === 'original' - ? this.portfolioPositions[symbol].investment - : this.portfolioPositions[symbol].value; + ? this.portfolioDetails.holdings[symbol].investment + : this.portfolioDetails.holdings[symbol].value; } } } @@ -211,7 +208,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { public onChangePeriod(aValue: string) { this.period = aValue; - this.initializeAnalysisData(this.portfolioPositions, this.period); + this.initializeAnalysisData(this.period); } public ngOnDestroy() { diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index b9d029119..1d3b56361 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -20,8 +20,8 @@ import { AdminData, Export, InfoItem, + PortfolioDetails, PortfolioPerformance, - PortfolioPosition, PortfolioReport, PortfolioSummary, User @@ -148,17 +148,16 @@ export class DataService { return this.http.get('/api/portfolio/investments'); } - public fetchPortfolioPerformance(aParams: { [param: string]: any }) { - return this.http.get('/api/portfolio/performance', { + public fetchPortfolioDetails(aParams: { [param: string]: any }) { + return this.http.get('/api/portfolio/details', { params: aParams }); } - public fetchPortfolioPositions(aParams: { [param: string]: any }) { - return this.http.get<{ [symbol: string]: PortfolioPosition }>( - '/api/portfolio/details', - { params: aParams } - ); + public fetchPortfolioPerformance(aParams: { [param: string]: any }) { + return this.http.get('/api/portfolio/performance', { + params: aParams + }); } public fetchPortfolioReport() { diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index 725b33921..15357d306 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -2,6 +2,7 @@ import { Access } from './access.interface'; import { AdminData } from './admin-data.interface'; import { Export } from './export.interface'; import { InfoItem } from './info-item.interface'; +import { PortfolioDetails } from './portfolio-details.interface'; import { PortfolioItem } from './portfolio-item.interface'; import { PortfolioOverview } from './portfolio-overview.interface'; import { PortfolioPerformance } from './portfolio-performance.interface'; @@ -20,6 +21,7 @@ export { AdminData, Export, InfoItem, + PortfolioDetails, PortfolioItem, PortfolioOverview, PortfolioPerformance, diff --git a/libs/common/src/lib/interfaces/portfolio-details.interface.ts b/libs/common/src/lib/interfaces/portfolio-details.interface.ts new file mode 100644 index 000000000..3a0d2bb09 --- /dev/null +++ b/libs/common/src/lib/interfaces/portfolio-details.interface.ts @@ -0,0 +1,8 @@ +import { PortfolioPosition } from '@ghostfolio/common/interfaces'; + +export interface PortfolioDetails { + accounts: { + [name: string]: { current: number; original: number }; + }; + holdings: { [symbol: string]: PortfolioPosition }; +}