Browse Source

Feature/switch to new performance calculation (#1336)

* Switch to new performance calculation

* Update changelog
pull/1337/head
Thomas Kaul 2 years ago
committed by GitHub
parent
commit
3fc2228f1d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 76
      apps/api/src/app/portfolio/portfolio.controller.ts
  3. 87
      apps/api/src/app/portfolio/portfolio.service.ts
  4. 26
      apps/client/src/app/components/home-overview/home-overview.component.ts
  5. 3
      apps/client/src/app/components/home-overview/home-overview.html
  6. 5
      apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts
  7. 2
      apps/client/src/app/pages/portfolio/analysis/analysis-page.html
  8. 17
      apps/client/src/app/services/data.service.ts

1
CHANGELOG.md

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Moved the benchmark comparator from experimental to general availability
- Improved the user interface of the benchmark comparator - Improved the user interface of the benchmark comparator
### Fixed ### Fixed

76
apps/api/src/app/portfolio/portfolio.controller.ts

@ -12,7 +12,6 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration.ser
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { import {
PortfolioChart,
PortfolioDetails, PortfolioDetails,
PortfolioInvestments, PortfolioInvestments,
PortfolioPerformanceResponse, PortfolioPerformanceResponse,
@ -61,55 +60,6 @@ export class PortfolioController {
this.baseCurrency = this.configurationService.get('BASE_CURRENCY'); this.baseCurrency = this.configurationService.get('BASE_CURRENCY');
} }
@Get('chart')
@UseGuards(AuthGuard('jwt'))
public async getChart(
@Headers('impersonation-id') impersonationId: string,
@Query('range') range
): Promise<PortfolioChart> {
const historicalDataContainer = await this.portfolioService.getChart(
impersonationId,
range
);
let chartData = historicalDataContainer.items;
let hasError = false;
chartData.forEach((chartDataItem) => {
if (hasNotDefinedValuesInObject(chartDataItem)) {
hasError = true;
}
});
if (
impersonationId ||
this.userService.isRestrictedView(this.request.user)
) {
let maxValue = 0;
chartData.forEach((portfolioItem) => {
if (portfolioItem.value > maxValue) {
maxValue = portfolioItem.value;
}
});
chartData = chartData.map((historicalDataItem) => {
return {
...historicalDataItem,
marketPrice: Number((historicalDataItem.value / maxValue).toFixed(2))
};
});
}
return {
hasError,
chart: chartData,
isAllTimeHigh: historicalDataContainer.isAllTimeHigh,
isAllTimeLow: historicalDataContainer.isAllTimeLow
};
}
@Get('details') @Get('details')
@UseGuards(AuthGuard('jwt')) @UseGuards(AuthGuard('jwt'))
@UseInterceptors(RedactValuesInResponseInterceptor) @UseInterceptors(RedactValuesInResponseInterceptor)
@ -274,32 +224,6 @@ export class PortfolioController {
return { firstOrderDate: parseDate(investments[0]?.date), investments }; return { firstOrderDate: parseDate(investments[0]?.date), investments };
} }
@Get('performance')
@UseGuards(AuthGuard('jwt'))
@UseInterceptors(TransformDataSourceInResponseInterceptor)
public async getPerformance(
@Headers('impersonation-id') impersonationId: string,
@Query('range') range
): Promise<PortfolioPerformanceResponse> {
const performanceInformation = await this.portfolioService.getPerformance(
impersonationId,
range
);
if (
impersonationId ||
this.request.user.Settings.settings.viewMode === 'ZEN' ||
this.userService.isRestrictedView(this.request.user)
) {
performanceInformation.performance = nullifyValuesInObject(
performanceInformation.performance,
['currentGrossPerformance', 'currentValue']
);
}
return performanceInformation;
}
@Get('performance') @Get('performance')
@UseGuards(AuthGuard('jwt')) @UseGuards(AuthGuard('jwt'))
@UseInterceptors(TransformDataSourceInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor)

87
apps/api/src/app/portfolio/portfolio.service.ts

@ -615,7 +615,7 @@ export class PortfolioService {
withExcludedAccounts withExcludedAccounts
}); });
const summary = await this.getSummary(impersonationId); const summary = await this.getSummary({ impersonationId });
return { return {
accounts, accounts,
@ -956,77 +956,6 @@ export class PortfolioService {
}; };
} }
public async getPerformance(
aImpersonationId: string,
aDateRange: DateRange = 'max'
): Promise<PortfolioPerformanceResponse> {
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
const { portfolioOrders, transactionPoints } =
await this.getTransactionPoints({
userId
});
const portfolioCalculator = new PortfolioCalculator({
currency: this.request.user.Settings.settings.baseCurrency,
currentRateService: this.currentRateService,
orders: portfolioOrders
});
if (transactionPoints?.length <= 0) {
return {
hasErrors: false,
performance: {
currentGrossPerformance: 0,
currentGrossPerformancePercent: 0,
currentNetPerformance: 0,
currentNetPerformancePercent: 0,
currentValue: 0
}
};
}
portfolioCalculator.setTransactionPoints(transactionPoints);
const portfolioStart = parseDate(transactionPoints[0].date);
const startDate = this.getStartDate(aDateRange, portfolioStart);
const currentPositions = await portfolioCalculator.getCurrentPositions(
startDate
);
const hasErrors = currentPositions.hasErrors;
const currentValue = currentPositions.currentValue.toNumber();
const currentGrossPerformance = currentPositions.grossPerformance;
let currentGrossPerformancePercent =
currentPositions.grossPerformancePercentage;
const currentNetPerformance = currentPositions.netPerformance;
let currentNetPerformancePercent =
currentPositions.netPerformancePercentage;
if (currentGrossPerformance.mul(currentGrossPerformancePercent).lt(0)) {
// If algebraic sign is different, harmonize it
currentGrossPerformancePercent = currentGrossPerformancePercent.mul(-1);
}
if (currentNetPerformance.mul(currentNetPerformancePercent).lt(0)) {
// If algebraic sign is different, harmonize it
currentNetPerformancePercent = currentNetPerformancePercent.mul(-1);
}
return {
errors: currentPositions.errors,
hasErrors: currentPositions.hasErrors || hasErrors,
performance: {
currentValue,
currentGrossPerformance: currentGrossPerformance.toNumber(),
currentGrossPerformancePercent:
currentGrossPerformancePercent.toNumber(),
currentNetPerformance: currentNetPerformance.toNumber(),
currentNetPerformancePercent: currentNetPerformancePercent.toNumber()
}
};
}
public async getPerformanceV2({ public async getPerformanceV2({
dateRange = 'max', dateRange = 'max',
impersonationId impersonationId
@ -1393,14 +1322,18 @@ export class PortfolioService {
return portfolioStart; return portfolioStart;
} }
private async getSummary( private async getSummary({
aImpersonationId: string impersonationId
): Promise<PortfolioSummary> { }: {
impersonationId: string;
}): Promise<PortfolioSummary> {
const userCurrency = this.request.user.Settings.settings.baseCurrency; const userCurrency = this.request.user.Settings.settings.baseCurrency;
const userId = await this.getUserId(aImpersonationId, this.request.user.id); const userId = await this.getUserId(impersonationId, this.request.user.id);
const user = await this.userService.user({ id: userId }); const user = await this.userService.user({ id: userId });
const performanceInformation = await this.getPerformance(aImpersonationId); const performanceInformation = await this.getPerformanceV2({
impersonationId
});
const { balanceInBaseCurrency } = await this.accountService.getCashDetails({ const { balanceInBaseCurrency } = await this.accountService.getCashDetails({
userId, userId,

26
apps/client/src/app/components/home-overview/home-overview.component.ts

@ -107,8 +107,7 @@ export class HomeOverviewComponent implements OnDestroy, OnInit {
this.dataService this.dataService
.fetchPortfolioPerformance({ .fetchPortfolioPerformance({
range: this.user?.settings?.dateRange, range: this.user?.settings?.dateRange
version: this.user?.settings?.isExperimentalFeatures ? 2 : 1
}) })
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => { .subscribe((response) => {
@ -117,35 +116,12 @@ export class HomeOverviewComponent implements OnDestroy, OnInit {
this.performance = response.performance; this.performance = response.performance;
this.isLoadingPerformance = false; this.isLoadingPerformance = false;
if (this.user?.settings?.isExperimentalFeatures) {
this.historicalDataItems = response.chart.map(({ date, value }) => { this.historicalDataItems = response.chart.map(({ date, value }) => {
return { return {
date, date,
value value
}; };
}); });
} else {
this.dataService
.fetchChart({
range: this.user?.settings?.dateRange,
version: 1
})
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((chartData) => {
this.historicalDataItems = chartData.chart.map(
({ date, value }) => {
return {
date,
value
};
}
);
this.isAllTimeHigh = chartData.isAllTimeHigh;
this.isAllTimeLow = chartData.isAllTimeLow;
this.changeDetectorRef.markForCheck();
});
}
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });

3
apps/client/src/app/components/home-overview/home-overview.html

@ -15,7 +15,7 @@
<gf-line-chart <gf-line-chart
class="position-absolute" class="position-absolute"
symbol="Performance" symbol="Performance"
[currency]="user?.settings?.isExperimentalFeatures ? undefined : user?.settings?.baseCurrency" unit="%"
[historicalDataItems]="historicalDataItems" [historicalDataItems]="historicalDataItems"
[hidden]="historicalDataItems?.length === 0" [hidden]="historicalDataItems?.length === 0"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
@ -24,7 +24,6 @@
[showLoader]="false" [showLoader]="false"
[showXAxis]="false" [showXAxis]="false"
[showYAxis]="false" [showYAxis]="false"
[unit]="user?.settings?.isExperimentalFeatures ? '%' : undefined"
></gf-line-chart> ></gf-line-chart>
</div> </div>
</div> </div>

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

@ -122,13 +122,11 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
} }
private update() { private update() {
if (this.user.settings.isExperimentalFeatures) {
this.isLoadingBenchmarkComparator = true; this.isLoadingBenchmarkComparator = true;
this.dataService this.dataService
.fetchPortfolioPerformance({ .fetchPortfolioPerformance({
range: this.user?.settings?.dateRange, range: this.user?.settings?.dateRange
version: 2
}) })
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ chart }) => { .subscribe(({ chart }) => {
@ -139,7 +137,6 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
}
this.dataService this.dataService
.fetchInvestments() .fetchInvestments()

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

@ -1,6 +1,6 @@
<div class="container"> <div class="container">
<h3 class="d-flex justify-content-center mb-3" i18n>Analysis</h3> <h3 class="d-flex justify-content-center mb-3" i18n>Analysis</h3>
<div *ngIf="user?.settings?.isExperimentalFeatures" class="mb-5 row"> <div class="mb-5 row">
<div class="col-lg"> <div class="col-lg">
<gf-benchmark-comparator <gf-benchmark-comparator
class="h-100" class="h-100"

17
apps/client/src/app/services/data.service.ts

@ -25,7 +25,6 @@ import {
Filter, Filter,
InfoItem, InfoItem,
OAuthResponse, OAuthResponse,
PortfolioChart,
PortfolioDetails, PortfolioDetails,
PortfolioInvestments, PortfolioInvestments,
PortfolioPerformanceResponse, PortfolioPerformanceResponse,
@ -138,12 +137,6 @@ export class DataService {
return this.http.get<BenchmarkResponse>('/api/v1/benchmark'); return this.http.get<BenchmarkResponse>('/api/v1/benchmark');
} }
public fetchChart({ range, version }: { range: DateRange; version: number }) {
return this.http.get<PortfolioChart>(`/api/v${version}/portfolio/chart`, {
params: { range }
});
}
public fetchExport(activityIds?: string[]) { public fetchExport(activityIds?: string[]) {
let params = new HttpParams(); let params = new HttpParams();
@ -259,15 +252,9 @@ export class DataService {
); );
} }
public fetchPortfolioPerformance({ public fetchPortfolioPerformance({ range }: { range: DateRange }) {
range,
version
}: {
range: DateRange;
version: number;
}) {
return this.http.get<PortfolioPerformanceResponse>( return this.http.get<PortfolioPerformanceResponse>(
`/api/v${version}/portfolio/performance`, `/api/v2/portfolio/performance`,
{ params: { range } } { params: { range } }
); );
} }

Loading…
Cancel
Save