Feature/switch to new performance calculation (#1336)
* Switch to new performance calculation * Update changelog
This commit is contained in:
parent
b018819a1f
commit
3fc2228f1d
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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();
|
||||||
});
|
});
|
||||||
|
@ -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>
|
||||||
|
@ -122,24 +122,21 @@ 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 }) => {
|
this.firstOrderDate = new Date(chart?.[0]?.date ?? new Date());
|
||||||
this.firstOrderDate = new Date(chart?.[0]?.date ?? new Date());
|
this.performanceDataItems = chart;
|
||||||
this.performanceDataItems = chart;
|
|
||||||
|
|
||||||
this.updateBenchmarkDataItems();
|
this.updateBenchmarkDataItems();
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchInvestments()
|
.fetchInvestments()
|
||||||
|
@ -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"
|
||||||
|
@ -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…
x
Reference in New Issue
Block a user