Feature/allocations by etf holding (#3464)
* Setup allocations by ETF holding * Update changelog
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
:host {
|
||||
display: block;
|
||||
|
||||
.mat-mdc-table {
|
||||
.gf-table {
|
||||
th {
|
||||
::ng-deep {
|
||||
.mat-sort-header-container {
|
||||
|
@@ -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) {
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user