From b6c24a53af09fd673959ccb0feda5669ab1e89d8 Mon Sep 17 00:00:00 2001 From: Daniel Devaud Date: Wed, 20 Dec 2023 14:58:38 +0100 Subject: [PATCH] Added Time Weighted Performance to front end --- .../src/app/portfolio/portfolio-calculator.ts | 49 +++++++++++++------ .../src/app/user/update-user-setting.dto.ts | 4 -- .../benchmark-comparator.component.ts | 1 + .../analysis/analysis-page.component.ts | 27 ++++------ .../portfolio/analysis/analysis-page.html | 6 +-- .../lib/interfaces/user-settings.interface.ts | 1 - 6 files changed, 49 insertions(+), 39 deletions(-) diff --git a/apps/api/src/app/portfolio/portfolio-calculator.ts b/apps/api/src/app/portfolio/portfolio-calculator.ts index e71f59478..b165ed5c6 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator.ts @@ -240,15 +240,15 @@ export class PortfolioCalculator { this.getRelevantStartAndEndDates(start, end, dates, step); const dataGartheringDates = [ ...dates, - ...this.orders.map((o) => { - let dateParsed = Date.parse(o.date); - if (isBefore(dateParsed, end) && isAfter(dateParsed, start)) { - let date = new Date(dateParsed); - if (dates.indexOf(date) === -1) { - return date; - } - } - }) + ...this.orders + .filter((o) => { + let dateParsed = Date.parse(o.date); + return isBefore(dateParsed, end) && isAfter(dateParsed, start); + }) + .map((o) => { + let dateParsed = Date.parse(o.date); + return new Date(dateParsed); + }) ]; const { dataProviderInfos, values: marketSymbols } = @@ -285,12 +285,26 @@ export class PortfolioCalculator { valuesBySymbol ); + let valuesBySymbolShortend: { + [symbol: string]: { + currentValues: { [date: string]: Big }; + investmentValues: { [date: string]: Big }; + maxInvestmentValues: { [date: string]: Big }; + netPerformanceValues: { [date: string]: Big }; + netPerformanceValuesPercentage: { [date: string]: Big }; + }; + } = {}; + Object.keys(valuesBySymbol).forEach((k) => { + if (valuesBySymbol[k].currentValues) { + Object.assign(valuesBySymbolShortend, { [k]: valuesBySymbol[k] }); + } + }); return dates.map((date: Date, index: number, dates: Date[]) => { let previousDate: Date = index > 0 ? dates[index - 1] : null; return this.calculatePerformance( date, previousDate, - valuesBySymbol, + valuesBySymbolShortend, calculateTimeWeightedPerformance ); }); @@ -347,17 +361,23 @@ export class PortfolioCalculator { ); if ( - calculateTimeWeightedPerformance && previousTotalInvestmentValue.toNumber() && - symbolValues.netPerformanceValuesPercentage + symbolValues.netPerformanceValuesPercentage && + ( + symbolValues.currentValues?.[previousDateString] ?? new Big(0) + ).toNumber() ) { const previousValue = symbolValues.currentValues?.[previousDateString] ?? new Big(0); const netPerformance = symbolValues.netPerformanceValuesPercentage?.[dateString] ?? new Big(0); + const timeWeightedPerformanceContribution = previousValue + .div(previousTotalInvestmentValue) + .mul(netPerformance) + .mul(100); timeWeightedPerformance = timeWeightedPerformance.plus( - previousValue.div(previousTotalInvestmentValue).mul(netPerformance) + timeWeightedPerformanceContribution ); } } @@ -1590,7 +1610,7 @@ export class PortfolioCalculator { .minus(1); } else if ( order.type === 'STAKE' && - marketSymbolMap[order.date][order.symbol] && + marketSymbolMap[order.date] && ((marketSymbolMap[previousOrder.date][ previousOrder.symbol ]?.toNumber() && @@ -1613,6 +1633,7 @@ export class PortfolioCalculator { netPerformanceValuesPercentage[order.date] = new Big(-1); } else if ( previousOrder.type === 'STAKE' && + marketSymbolMap[previousOrder.date] && marketSymbolMap[previousOrder.date][previousOrder.symbol]?.toNumber() ) { netPerformanceValuesPercentage[order.date] = order.unitPrice diff --git a/apps/api/src/app/user/update-user-setting.dto.ts b/apps/api/src/app/user/update-user-setting.dto.ts index 1c8e42cd0..b1967faba 100644 --- a/apps/api/src/app/user/update-user-setting.dto.ts +++ b/apps/api/src/app/user/update-user-setting.dto.ts @@ -68,8 +68,4 @@ export class UpdateUserSettingDto { @IsIn(['DEFAULT', 'ZEN']) @IsOptional() viewMode?: ViewMode; - - @IsIn(['N', 'B', 'O']) - @IsOptional() - timeWeightedPerformance?: string; } diff --git a/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts b/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts index dbb0aa792..05a14d43a 100644 --- a/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts +++ b/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -116,6 +116,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy { backgroundColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`, borderColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`, borderWidth: 2, + borderDash: [5, 5], data: this.timeWeightedPerformanceDataItems.map(({ date, value }) => { return { x: parseDate(date).getTime(), y: value }; }), diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts index c27ab5b65..f28b8f272 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts @@ -68,6 +68,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit { public placeholder = ''; public portfolioEvolutionDataLabel = $localize`Deposit`; public streaks: PortfolioInvestments['streaks']; + public timeWeightedPerformance: string = 'N'; public top3: Position[]; public unitCurrentStreak: string; public unitLongestStreak: string; @@ -222,21 +223,9 @@ export class AnalysisPageComponent implements OnDestroy, OnInit { } public onTimeWeightedPerformanceChanged(timeWeightedPerformance: string) { - this.dataService - .putUserSetting({ timeWeightedPerformance }) - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(() => { - this.userService.remove(); + this.timeWeightedPerformance = timeWeightedPerformance; - this.userService - .get() - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe((user) => { - this.user = user; - - this.changeDetectorRef.markForCheck(); - }); - }); + this.update(); } public onChangeGroupBy(aMode: GroupBy) { @@ -343,7 +332,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit { filters: this.activeFilters, range: this.user?.settings?.dateRange, timeWeightedPerformance: - this.user?.settings?.timeWeightedPerformance === 'N' ? false : true + this.timeWeightedPerformance === 'N' ? false : true }) .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(({ chart, firstOrderDate }) => { @@ -378,15 +367,19 @@ export class AnalysisPageComponent implements OnDestroy, OnInit { date, value: netPerformanceInPercentage }); - if ((this.user?.settings?.timeWeightedPerformance ?? 'N') !== 'N') { + if ((this.timeWeightedPerformance ?? 'N') !== 'N') { let lastPerformance = 0; if (index > 0) { lastPerformance = new Big( chart[index - 1].timeWeightedPerformance ) + .div(100) .plus(1) - .mul(new Big(chart[index].timeWeightedPerformance).plus(1)) + .mul( + new Big(chart[index].timeWeightedPerformance).div(100).plus(1) + ) .minus(1) + .mul(100) .toNumber(); } chart[index].timeWeightedPerformance = lastPerformance; diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.html b/apps/client/src/app/pages/portfolio/analysis/analysis-page.html index 7e15345bb..ace412ce1 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.html +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -25,8 +25,8 @@ [daysInMarket]="daysInMarket" [isLoading]="isLoadingBenchmarkComparator || isLoadingInvestmentChart" [locale]="user?.settings?.locale" - [performanceDataItems]="performanceDataItemsInPercentage" - [timeWeightedPerformanceDataItems]="performanceDataItemsTimeWeightedInPercentage" + [performanceDataItems]="timeWeightedPerformance === 'O' ? [] :performanceDataItemsInPercentage" + [timeWeightedPerformanceDataItems]="timeWeightedPerformance === 'N' ? [] :performanceDataItemsTimeWeightedInPercentage" [user]="user" (benchmarkChanged)="onChangeBenchmark($event)" > @@ -37,7 +37,7 @@ > Include time-weighted performance