diff --git a/apps/api/src/app/core/portfolio-calculator.ts b/apps/api/src/app/core/portfolio-calculator.ts index 438f86396..75aab355f 100644 --- a/apps/api/src/app/core/portfolio-calculator.ts +++ b/apps/api/src/app/core/portfolio-calculator.ts @@ -311,6 +311,23 @@ export class PortfolioCalculator { }; } + public getInvestments(): { date: string; investment: Big }[] { + if (this.transactionPoints.length === 0) { + return []; + } + + return this.transactionPoints.map((transactionPoint) => { + return { + date: transactionPoint.date, + investment: transactionPoint.items.reduce( + (investment, transactionPointSymbol) => + investment.add(transactionPointSymbol.investment), + new Big(0) + ) + }; + }); + } + public async calculateTimeline( timelineSpecification: TimelineSpecification[], endDate: string diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index bbef3b446..a7f5aa197 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -39,6 +39,7 @@ import { } from './interfaces/portfolio-position-detail.interface'; import { PortfolioPositions } from './interfaces/portfolio-positions.interface'; import { PortfolioService } from './portfolio.service'; +import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface'; @Controller('portfolio') export class PortfolioController { @@ -49,12 +50,14 @@ export class PortfolioController { @Inject(REQUEST) private readonly request: RequestWithUser ) {} - @Get() + @Get('/investments') @UseGuards(AuthGuard('jwt')) public async findAll( @Headers('impersonation-id') impersonationId - ): Promise { - let portfolio = await this.portfolioService.findAll(impersonationId); + ): Promise { + let investments = await this.portfolioService.getInvestments( + impersonationId + ); if ( impersonationId && @@ -63,25 +66,18 @@ export class PortfolioController { permissions.readForeignPortfolio ) ) { - portfolio = portfolio.map((portfolioItem) => { - Object.keys(portfolioItem.positions).forEach((symbol) => { - portfolioItem.positions[symbol].investment = - portfolioItem.positions[symbol].investment > 0 ? 1 : 0; - portfolioItem.positions[symbol].investmentInOriginalCurrency = - portfolioItem.positions[symbol].investmentInOriginalCurrency > 0 - ? 1 - : 0; - portfolioItem.positions[symbol].quantity = - portfolioItem.positions[symbol].quantity > 0 ? 1 : 0; - }); - - portfolioItem.investment = null; - - return portfolioItem; - }); + const maxInvestment = investments.reduce( + (investment, item) => Math.max(investment, item.investment), + 1 + ); + + investments = investments.map((item) => ({ + date: item.date, + investment: item.investment / maxInvestment + })); } - return portfolio; + return investments; } @Get('chart') diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 08dc44f6b..99b235a68 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -63,6 +63,7 @@ import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.se import { UNKNOWN_KEY } from '@ghostfolio/common/config'; import { EnhancedSymbolProfile } from '@ghostfolio/api/services/interfaces/symbol-profile.interface'; import { TransactionPoint } from '@ghostfolio/api/app/core/interfaces/transaction-point.interface'; +import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface'; @Injectable() export class PortfolioService { @@ -136,34 +137,35 @@ export class PortfolioService { return portfolio; } - public async findAll(aImpersonationId: string): Promise { - try { - const impersonationUserId = - await this.impersonationService.validateImpersonationId( - aImpersonationId, - this.request.user.id - ); + public async getInvestments( + aImpersonationId: string + ): Promise { + const userId = await this.getUserId(aImpersonationId); - const portfolio = await this.createPortfolio( - impersonationUserId || this.request.user.id - ); - return portfolio.get(); - } catch (error) { - console.error(error); + const portfolioCalculator = new PortfolioCalculator( + this.currentRateService, + this.request.user.Settings.currency + ); + + const { transactionPoints } = await this.getTransactionPoints(userId); + portfolioCalculator.setTransactionPoints(transactionPoints); + if (transactionPoints.length === 0) { + return []; } + + return portfolioCalculator.getInvestments().map((item) => { + return { + date: item.date, + investment: item.investment.toNumber() + }; + }); } public async getChart( aImpersonationId: string, aDateRange: DateRange = 'max' ): Promise { - const impersonationUserId = - await this.impersonationService.validateImpersonationId( - aImpersonationId, - this.request.user.id - ); - - const userId = impersonationUserId || this.request.user.id; + const userId = await this.getUserId(aImpersonationId); const portfolioCalculator = new PortfolioCalculator( this.currentRateService, diff --git a/apps/client/src/app/components/investment-chart/investment-chart.component.ts b/apps/client/src/app/components/investment-chart/investment-chart.component.ts index 7dad55b7c..c9056a94a 100644 --- a/apps/client/src/app/components/investment-chart/investment-chart.component.ts +++ b/apps/client/src/app/components/investment-chart/investment-chart.component.ts @@ -10,16 +10,16 @@ import { ViewChild } from '@angular/core'; import { primaryColorRgb } from '@ghostfolio/common/config'; -import { PortfolioItem } from '@ghostfolio/common/interfaces'; import { + Chart, + LinearScale, LineController, LineElement, - LinearScale, PointElement, TimeScale } from 'chart.js'; -import { Chart } from 'chart.js'; import { addMonths, isAfter, parseISO, subMonths } from 'date-fns'; +import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface'; @Component({ selector: 'gf-investment-chart', @@ -28,7 +28,7 @@ import { addMonths, isAfter, parseISO, subMonths } from 'date-fns'; styleUrls: ['./investment-chart.component.scss'] }) export class InvestmentChartComponent implements OnChanges, OnDestroy, OnInit { - @Input() portfolioItems: PortfolioItem[]; + @Input() investments: InvestmentItem[]; @ViewChild('chartCanvas') chartCanvas; @@ -48,7 +48,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy, OnInit { public ngOnInit() {} public ngOnChanges() { - if (this.portfolioItems) { + if (this.investments) { this.initialize(); } } @@ -60,32 +60,32 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy, OnInit { private initialize() { this.isLoading = true; - if (this.portfolioItems?.length > 0) { + if (this.investments?.length > 0) { // Extend chart by three months (before) - const firstItem = this.portfolioItems[0]; - this.portfolioItems.unshift({ + const firstItem = this.investments[0]; + this.investments.unshift({ ...firstItem, date: subMonths(parseISO(firstItem.date), 3).toISOString(), investment: 0 }); // Extend chart by three months (after) - const lastItem = this.portfolioItems[this.portfolioItems.length - 1]; - this.portfolioItems.push({ + const lastItem = this.investments[this.investments.length - 1]; + this.investments.push({ ...lastItem, date: addMonths(parseISO(lastItem.date), 3).toISOString() }); } const data = { - labels: this.portfolioItems.map((position) => { + labels: this.investments.map((position) => { return position.date; }), datasets: [ { borderColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`, borderWidth: 2, - data: this.portfolioItems.map((position) => { + data: this.investments.map((position) => { return position.investment; }), segment: { diff --git a/apps/client/src/app/pages/tools/analysis/analysis-page.component.ts b/apps/client/src/app/pages/tools/analysis/analysis-page.component.ts index a8ab3e98e..9ec8b2d78 100644 --- a/apps/client/src/app/pages/tools/analysis/analysis-page.component.ts +++ b/apps/client/src/app/pages/tools/analysis/analysis-page.component.ts @@ -12,6 +12,7 @@ import { import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; +import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface'; @Component({ selector: 'gf-analysis-page', @@ -35,7 +36,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit { { label: 'Initial', value: 'original' }, { label: 'Current', value: 'current' } ]; - public portfolioItems: PortfolioItem[]; + public investments: InvestmentItem[]; public portfolioPositions: { [symbol: string]: PortfolioPosition }; public positions: { [symbol: string]: any }; public positionsArray: PortfolioPosition[]; @@ -71,10 +72,10 @@ export class AnalysisPageComponent implements OnDestroy, OnInit { }); this.dataService - .fetchPortfolio() + .fetchInvestments() .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((response) => { - this.portfolioItems = response; + this.investments = response; this.changeDetectorRef.markForCheck(); }); diff --git a/apps/client/src/app/pages/tools/analysis/analysis-page.html b/apps/client/src/app/pages/tools/analysis/analysis-page.html index 4223ced07..37137839b 100644 --- a/apps/client/src/app/pages/tools/analysis/analysis-page.html +++ b/apps/client/src/app/pages/tools/analysis/analysis-page.html @@ -202,7 +202,7 @@ diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 91b5741e2..b6e9d5dd7 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -36,6 +36,7 @@ import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { SettingsStorageService } from './settings-storage.service'; +import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface'; @Injectable({ providedIn: 'root' @@ -143,8 +144,8 @@ export class DataService { ); } - public fetchPortfolio() { - return this.http.get('/api/portfolio'); + public fetchInvestments() { + return this.http.get('/api/portfolio/investments'); } public fetchPortfolioOverview() { diff --git a/libs/common/src/lib/interfaces/investment-item.interface.ts b/libs/common/src/lib/interfaces/investment-item.interface.ts new file mode 100644 index 000000000..effa18c1b --- /dev/null +++ b/libs/common/src/lib/interfaces/investment-item.interface.ts @@ -0,0 +1,4 @@ +export interface InvestmentItem { + date: string; + investment: number; +}