Feature/improve allocations by etf holding (#3467)

* Improve allocations by ETF holding

* Update changelog
This commit is contained in:
Thomas Kaul
2024-06-08 11:17:27 +02:00
committed by GitHub
parent fdcf5fd396
commit 23e4d5454d
11 changed files with 161 additions and 88 deletions

View File

@@ -3,7 +3,7 @@ import { AccountDetailDialogParams } from '@ghostfolio/client/components/account
import { DataService } from '@ghostfolio/client/services/data.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
import { MAX_TOP_HOLDINGS, UNKNOWN_KEY } from '@ghostfolio/common/config';
import { prettifySymbol } from '@ghostfolio/common/helper';
import {
Holding,
@@ -85,7 +85,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
value: number;
};
};
public topHoldings: Holding[] = [];
public topHoldings: Holding[];
public topHoldingsMap: {
[name: string]: { name: string; value: number };
};
@@ -456,21 +456,28 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
for (const holding of position.holdings) {
const { name, valueInBaseCurrency } = holding;
if (this.topHoldingsMap[name]?.value) {
this.topHoldingsMap[name].value +=
valueInBaseCurrency *
(isNumber(position.valueInBaseCurrency)
? position.valueInBaseCurrency
: position.valueInPercentage);
} else {
this.topHoldingsMap[name] = {
name,
value:
if (
!this.hasImpersonationId &&
!this.user.settings.isRestrictedView
) {
if (this.topHoldingsMap[name]?.value) {
this.topHoldingsMap[name].value +=
valueInBaseCurrency *
(isNumber(position.valueInBaseCurrency)
? this.portfolioDetails.holdings[symbol].valueInBaseCurrency
: this.portfolioDetails.holdings[symbol].valueInPercentage)
};
? position.valueInBaseCurrency
: position.valueInPercentage);
} else {
this.topHoldingsMap[name] = {
name,
value:
valueInBaseCurrency *
(isNumber(position.valueInBaseCurrency)
? this.portfolioDetails.holdings[symbol]
.valueInBaseCurrency
: this.portfolioDetails.holdings[symbol]
.valueInPercentage)
};
}
}
}
}
@@ -505,11 +512,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
}
}
if (
this.positions[symbol].assetSubClass === 'ETF' &&
!this.hasImpersonationId &&
!this.user.settings.isRestrictedView
) {
if (this.positions[symbol].assetSubClass === 'ETF') {
this.totalValueInEtf += this.positions[symbol].value;
}
@@ -557,19 +560,21 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
this.markets[UNKNOWN_KEY].value =
this.markets[UNKNOWN_KEY].value / marketsTotal;
if (!this.hasImpersonationId && !this.user.settings.isRestrictedView) {
this.topHoldings = Object.values(this.topHoldingsMap)
.map(({ name, value }) => {
return {
name,
allocationInPercentage:
this.totalValueInEtf > 0 ? value / this.totalValueInEtf : 0,
valueInBaseCurrency: value
};
})
.sort((a, b) => {
return b.valueInBaseCurrency - a.valueInBaseCurrency;
});
this.topHoldings = Object.values(this.topHoldingsMap)
.map(({ name, value }) => {
return {
name,
allocationInPercentage:
this.totalValueInEtf > 0 ? value / this.totalValueInEtf : 0,
valueInBaseCurrency: value
};
})
.sort((a, b) => {
return b.valueInBaseCurrency - a.valueInBaseCurrency;
});
if (this.topHoldings.length > MAX_TOP_HOLDINGS) {
this.topHoldings = this.topHoldings.slice(0, MAX_TOP_HOLDINGS);
}
}

View File

@@ -264,6 +264,30 @@
</div>
</div>
<div class="row">
<div class="col-md-4">
<mat-card appearance="outlined" class="mb-3">
<mat-card-header class="overflow-hidden w-100">
<mat-card-title class="align-items-center d-flex text-truncate"
><span i18n>By Country</span
><gf-premium-indicator
*ngIf="user?.subscription?.type === 'Basic'"
class="ml-1"
/>
</mat-card-title>
</mat-card-header>
<mat-card-content>
<gf-portfolio-proportion-chart
[baseCurrency]="user?.settings?.baseCurrency"
[colorScheme]="user?.settings?.colorScheme"
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
[keys]="['name']"
[locale]="user?.settings?.locale"
[maxItems]="10"
[positions]="countries"
/>
</mat-card-content>
</mat-card>
</div>
<div class="col-md-4">
<mat-card appearance="outlined" class="mb-3">
<mat-card-header class="overflow-hidden w-100">
@@ -306,57 +330,36 @@
</mat-card-content>
</mat-card>
</div>
<div class="col-md-4">
<div
class="col-md-12"
[ngClass]="{
'd-none': !user?.settings?.isExperimentalFeatures
}"
>
<mat-card appearance="outlined" class="mb-3">
<mat-card-header class="overflow-hidden w-100">
<mat-card-title class="align-items-center d-flex text-truncate"
><span i18n>By Country</span
><gf-premium-indicator
><span i18n>By ETF Holding</span>
<gf-premium-indicator
*ngIf="user?.subscription?.type === 'Basic'"
class="ml-1"
/>
</mat-card-title>
<mat-card-subtitle>
<ng-container i18n
>Approximation based on the Top 15 holdings per ETF</ng-container
>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<gf-portfolio-proportion-chart
<gf-top-holdings
[baseCurrency]="user?.settings?.baseCurrency"
[colorScheme]="user?.settings?.colorScheme"
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
[keys]="['name']"
[locale]="user?.settings?.locale"
[maxItems]="10"
[positions]="countries"
[pageSize]="10"
[topHoldings]="topHoldings"
/>
</mat-card-content>
</mat-card>
</div>
@if (topHoldings?.length > 0 && user?.settings?.isExperimentalFeatures) {
<div class="col-md-12">
<mat-card appearance="outlined" class="mb-3">
<mat-card-header class="overflow-hidden w-100">
<mat-card-title class="align-items-center d-flex text-truncate"
><span i18n>By ETF Holding</span>
<gf-premium-indicator
*ngIf="user?.subscription?.type === 'Basic'"
class="ml-1"
/>
</mat-card-title>
<mat-card-subtitle>
<ng-container i18n
>Approximation based on the Top 15 holdings per
ETF</ng-container
>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<gf-top-holdings
[baseCurrency]="user?.settings?.baseCurrency"
[locale]="user?.settings?.locale"
[topHoldings]="topHoldings"
/>
</mat-card-content>
</mat-card>
</div>
}
</div>
</div>