Browse Source

Added Time Weighted Performance to front end

pull/5027/head
Daniel Devaud 2 years ago
parent
commit
b6c24a53af
  1. 45
      apps/api/src/app/portfolio/portfolio-calculator.ts
  2. 4
      apps/api/src/app/user/update-user-setting.dto.ts
  3. 1
      apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts
  4. 27
      apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts
  5. 6
      apps/client/src/app/pages/portfolio/analysis/analysis-page.html
  6. 1
      libs/common/src/lib/interfaces/user-settings.interface.ts

45
apps/api/src/app/portfolio/portfolio-calculator.ts

@ -240,14 +240,14 @@ export class PortfolioCalculator {
this.getRelevantStartAndEndDates(start, end, dates, step); this.getRelevantStartAndEndDates(start, end, dates, step);
const dataGartheringDates = [ const dataGartheringDates = [
...dates, ...dates,
...this.orders.map((o) => { ...this.orders
.filter((o) => {
let dateParsed = Date.parse(o.date); let dateParsed = Date.parse(o.date);
if (isBefore(dateParsed, end) && isAfter(dateParsed, start)) { return isBefore(dateParsed, end) && isAfter(dateParsed, start);
let date = new Date(dateParsed); })
if (dates.indexOf(date) === -1) { .map((o) => {
return date; let dateParsed = Date.parse(o.date);
} return new Date(dateParsed);
}
}) })
]; ];
@ -285,12 +285,26 @@ export class PortfolioCalculator {
valuesBySymbol 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[]) => { return dates.map((date: Date, index: number, dates: Date[]) => {
let previousDate: Date = index > 0 ? dates[index - 1] : null; let previousDate: Date = index > 0 ? dates[index - 1] : null;
return this.calculatePerformance( return this.calculatePerformance(
date, date,
previousDate, previousDate,
valuesBySymbol, valuesBySymbolShortend,
calculateTimeWeightedPerformance calculateTimeWeightedPerformance
); );
}); });
@ -347,17 +361,23 @@ export class PortfolioCalculator {
); );
if ( if (
calculateTimeWeightedPerformance &&
previousTotalInvestmentValue.toNumber() && previousTotalInvestmentValue.toNumber() &&
symbolValues.netPerformanceValuesPercentage symbolValues.netPerformanceValuesPercentage &&
(
symbolValues.currentValues?.[previousDateString] ?? new Big(0)
).toNumber()
) { ) {
const previousValue = const previousValue =
symbolValues.currentValues?.[previousDateString] ?? new Big(0); symbolValues.currentValues?.[previousDateString] ?? new Big(0);
const netPerformance = const netPerformance =
symbolValues.netPerformanceValuesPercentage?.[dateString] ?? symbolValues.netPerformanceValuesPercentage?.[dateString] ??
new Big(0); new Big(0);
const timeWeightedPerformanceContribution = previousValue
.div(previousTotalInvestmentValue)
.mul(netPerformance)
.mul(100);
timeWeightedPerformance = timeWeightedPerformance.plus( timeWeightedPerformance = timeWeightedPerformance.plus(
previousValue.div(previousTotalInvestmentValue).mul(netPerformance) timeWeightedPerformanceContribution
); );
} }
} }
@ -1590,7 +1610,7 @@ export class PortfolioCalculator {
.minus(1); .minus(1);
} else if ( } else if (
order.type === 'STAKE' && order.type === 'STAKE' &&
marketSymbolMap[order.date][order.symbol] && marketSymbolMap[order.date] &&
((marketSymbolMap[previousOrder.date][ ((marketSymbolMap[previousOrder.date][
previousOrder.symbol previousOrder.symbol
]?.toNumber() && ]?.toNumber() &&
@ -1613,6 +1633,7 @@ export class PortfolioCalculator {
netPerformanceValuesPercentage[order.date] = new Big(-1); netPerformanceValuesPercentage[order.date] = new Big(-1);
} else if ( } else if (
previousOrder.type === 'STAKE' && previousOrder.type === 'STAKE' &&
marketSymbolMap[previousOrder.date] &&
marketSymbolMap[previousOrder.date][previousOrder.symbol]?.toNumber() marketSymbolMap[previousOrder.date][previousOrder.symbol]?.toNumber()
) { ) {
netPerformanceValuesPercentage[order.date] = order.unitPrice netPerformanceValuesPercentage[order.date] = order.unitPrice

4
apps/api/src/app/user/update-user-setting.dto.ts

@ -68,8 +68,4 @@ export class UpdateUserSettingDto {
@IsIn(<ViewMode[]>['DEFAULT', 'ZEN']) @IsIn(<ViewMode[]>['DEFAULT', 'ZEN'])
@IsOptional() @IsOptional()
viewMode?: ViewMode; viewMode?: ViewMode;
@IsIn(<string[]>['N', 'B', 'O'])
@IsOptional()
timeWeightedPerformance?: string;
} }

1
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})`, backgroundColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`,
borderColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`, borderColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`,
borderWidth: 2, borderWidth: 2,
borderDash: [5, 5],
data: this.timeWeightedPerformanceDataItems.map(({ date, value }) => { data: this.timeWeightedPerformanceDataItems.map(({ date, value }) => {
return { x: parseDate(date).getTime(), y: value }; return { x: parseDate(date).getTime(), y: value };
}), }),

27
apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts

@ -68,6 +68,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
public placeholder = ''; public placeholder = '';
public portfolioEvolutionDataLabel = $localize`Deposit`; public portfolioEvolutionDataLabel = $localize`Deposit`;
public streaks: PortfolioInvestments['streaks']; public streaks: PortfolioInvestments['streaks'];
public timeWeightedPerformance: string = 'N';
public top3: Position[]; public top3: Position[];
public unitCurrentStreak: string; public unitCurrentStreak: string;
public unitLongestStreak: string; public unitLongestStreak: string;
@ -222,21 +223,9 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
} }
public onTimeWeightedPerformanceChanged(timeWeightedPerformance: string) { public onTimeWeightedPerformanceChanged(timeWeightedPerformance: string) {
this.dataService this.timeWeightedPerformance = timeWeightedPerformance;
.putUserSetting({ timeWeightedPerformance })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
this.userService.remove();
this.userService this.update();
.get()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((user) => {
this.user = user;
this.changeDetectorRef.markForCheck();
});
});
} }
public onChangeGroupBy(aMode: GroupBy) { public onChangeGroupBy(aMode: GroupBy) {
@ -343,7 +332,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
filters: this.activeFilters, filters: this.activeFilters,
range: this.user?.settings?.dateRange, range: this.user?.settings?.dateRange,
timeWeightedPerformance: timeWeightedPerformance:
this.user?.settings?.timeWeightedPerformance === 'N' ? false : true this.timeWeightedPerformance === 'N' ? false : true
}) })
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ chart, firstOrderDate }) => { .subscribe(({ chart, firstOrderDate }) => {
@ -378,15 +367,19 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
date, date,
value: netPerformanceInPercentage value: netPerformanceInPercentage
}); });
if ((this.user?.settings?.timeWeightedPerformance ?? 'N') !== 'N') { if ((this.timeWeightedPerformance ?? 'N') !== 'N') {
let lastPerformance = 0; let lastPerformance = 0;
if (index > 0) { if (index > 0) {
lastPerformance = new Big( lastPerformance = new Big(
chart[index - 1].timeWeightedPerformance chart[index - 1].timeWeightedPerformance
) )
.div(100)
.plus(1) .plus(1)
.mul(new Big(chart[index].timeWeightedPerformance).plus(1)) .mul(
new Big(chart[index].timeWeightedPerformance).div(100).plus(1)
)
.minus(1) .minus(1)
.mul(100)
.toNumber(); .toNumber();
} }
chart[index].timeWeightedPerformance = lastPerformance; chart[index].timeWeightedPerformance = lastPerformance;

6
apps/client/src/app/pages/portfolio/analysis/analysis-page.html

@ -25,8 +25,8 @@
[daysInMarket]="daysInMarket" [daysInMarket]="daysInMarket"
[isLoading]="isLoadingBenchmarkComparator || isLoadingInvestmentChart" [isLoading]="isLoadingBenchmarkComparator || isLoadingInvestmentChart"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
[performanceDataItems]="performanceDataItemsInPercentage" [performanceDataItems]="timeWeightedPerformance === 'O' ? [] :performanceDataItemsInPercentage"
[timeWeightedPerformanceDataItems]="performanceDataItemsTimeWeightedInPercentage" [timeWeightedPerformanceDataItems]="timeWeightedPerformance === 'N' ? [] :performanceDataItemsTimeWeightedInPercentage"
[user]="user" [user]="user"
(benchmarkChanged)="onChangeBenchmark($event)" (benchmarkChanged)="onChangeBenchmark($event)"
></gf-benchmark-comparator> ></gf-benchmark-comparator>
@ -37,7 +37,7 @@
> >
<span i18n>Include time-weighted performance </span> <span i18n>Include time-weighted performance </span>
<gf-toggle <gf-toggle
[defaultValue]="user?.settings?.timeWeightedPerformance ?? 'N'" [defaultValue]="timeWeightedPerformance"
[isLoading]="isLoadingBenchmarkComparator || isLoadingInvestmentChart" [isLoading]="isLoadingBenchmarkComparator || isLoadingInvestmentChart"
[options]="timeWeightedPerformanceOptions" [options]="timeWeightedPerformanceOptions"
(change)="onTimeWeightedPerformanceChanged($event.value)" (change)="onTimeWeightedPerformanceChanged($event.value)"

1
libs/common/src/lib/interfaces/user-settings.interface.ts

@ -15,5 +15,4 @@ export interface UserSettings {
retirementDate?: string; retirementDate?: string;
savingsRate?: number; savingsRate?: number;
viewMode?: ViewMode; viewMode?: ViewMode;
timeWeightedPerformance?: string;
} }

Loading…
Cancel
Save