Feature/allocations by etf holding (#3464)

* Setup allocations by ETF holding

* Update changelog
This commit is contained in:
Thomas Kaul
2024-06-07 21:45:07 +02:00
committed by GitHub
parent 3fb7e746df
commit 8a9ae9bb33
24 changed files with 302 additions and 9 deletions

View File

@@ -1,7 +1,7 @@
:host {
display: block;
.mat-mdc-table {
.gf-table {
th {
::ng-deep {
.mat-sort-header-container {

View File

@@ -6,6 +6,7 @@ import { UserService } from '@ghostfolio/client/services/user/user.service';
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
import { prettifySymbol } from '@ghostfolio/common/helper';
import {
Holding,
PortfolioDetails,
PortfolioPosition,
UniqueAsset,
@@ -84,6 +85,11 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
value: number;
};
};
public topHoldings: Holding[] = [];
public topHoldingsMap: {
[name: string]: { name: string; value: number };
};
public totalValueInEtf = 0;
public UNKNOWN_KEY = UNKNOWN_KEY;
public user: User;
public worldMapChartFormat: string;
@@ -288,6 +294,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
value: 0
}
};
this.topHoldingsMap = {};
}
private initializeAllocationsData() {
@@ -337,7 +344,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
};
if (position.assetClass !== AssetClass.LIQUIDITY) {
// Prepare analysis data by continents, countries and sectors except for liquidity
// Prepare analysis data by continents, countries, holdings and sectors except for liquidity
if (position.countries.length > 0) {
this.markets.developedMarkets.value +=
@@ -445,6 +452,29 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
: this.portfolioDetails.holdings[symbol].valueInPercentage;
}
if (position.holdings.length > 0) {
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:
valueInBaseCurrency *
(isNumber(position.valueInBaseCurrency)
? this.portfolioDetails.holdings[symbol].valueInBaseCurrency
: this.portfolioDetails.holdings[symbol].valueInPercentage)
};
}
}
}
if (position.sectors.length > 0) {
for (const sector of position.sectors) {
const { name, weight } = sector;
@@ -475,6 +505,14 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
}
}
if (
this.positions[symbol].assetSubClass === 'ETF' &&
!this.hasImpersonationId &&
!this.user.settings.isRestrictedView
) {
this.totalValueInEtf += this.positions[symbol].value;
}
this.symbols[prettifySymbol(symbol)] = {
dataSource: position.dataSource,
name: position.name,
@@ -518,6 +556,21 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
this.markets.otherMarkets.value / marketsTotal;
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;
});
}
}
private openAccountDetailDialog(aAccountId: string) {

View File

@@ -330,5 +330,33 @@
</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>

View File

@@ -1,6 +1,7 @@
import { GfWorldMapChartModule } from '@ghostfolio/client/components/world-map-chart/world-map-chart.module';
import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
import { GfTopHoldingsComponent } from '@ghostfolio/ui/top-holdings';
import { GfValueComponent } from '@ghostfolio/ui/value';
import { CommonModule } from '@angular/common';
@@ -19,8 +20,9 @@ import { AllocationsPageComponent } from './allocations-page.component';
CommonModule,
GfPortfolioProportionChartComponent,
GfPremiumIndicatorComponent,
GfWorldMapChartModule,
GfTopHoldingsComponent,
GfValueComponent,
GfWorldMapChartModule,
MatCardModule,
MatDialogModule,
MatProgressBarModule