diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts
index beb75001b..ff7a3e1db 100644
--- a/apps/api/src/app/portfolio/portfolio.controller.ts
+++ b/apps/api/src/app/portfolio/portfolio.controller.ts
@@ -177,6 +177,7 @@ export class PortfolioController {
? portfolioPosition.markets
: undefined;
portfolioPosition.sectors = hasDetails ? portfolioPosition.sectors : [];
+ portfolioPosition.tags = hasDetails ? portfolioPosition.tags : [];
}
for (const [name, { valueInBaseCurrency }] of Object.entries(accounts)) {
@@ -243,6 +244,7 @@ export class PortfolioController {
currency: hasDetails ? portfolioPosition.currency : undefined,
holdings: hasDetails ? portfolioPosition.holdings : [],
markets: hasDetails ? portfolioPosition.markets : undefined,
+ tags: hasDetails ? portfolioPosition.tags : [],
marketsAdvanced: hasDetails
? portfolioPosition.marketsAdvanced
: undefined,
diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts
index 207879b86..4e8f4de60 100644
--- a/apps/api/src/app/portfolio/portfolio.service.ts
+++ b/apps/api/src/app/portfolio/portfolio.service.ts
@@ -78,7 +78,7 @@ import {
parseISO,
set
} from 'date-fns';
-import { isEmpty, uniq } from 'lodash';
+import { isEmpty, uniq, uniqBy } from 'lodash';
import { CPRPortfolioCalculator } from './calculator/constantPortfolioReturn/portfolio-calculator';
import { PortfolioCalculator } from './calculator/portfolio-calculator';
@@ -486,13 +486,17 @@ export class PortfolioService {
}));
}
+ const tagsInternal = tags.concat(
+ symbolProfiles.find((sp) => sp.symbol === symbol)?.tags ?? []
+ );
+
holdings[symbol] = {
currency,
markets,
marketsAdvanced,
marketPrice,
symbol,
- tags,
+ tags: uniqBy(tagsInternal, 'id'),
transactionCount,
allocationInPercentage: filteredValueInBaseCurrency.eq(0)
? 0
diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
index 696fe7c5f..bedab34ea 100644
--- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
+++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
@@ -7,6 +7,7 @@ import { MAX_TOP_HOLDINGS, UNKNOWN_KEY } from '@ghostfolio/common/config';
import { prettifySymbol } from '@ghostfolio/common/helper';
import {
AssetProfileIdentifier,
+ Holding,
HoldingWithParents,
PortfolioDetails,
PortfolioPosition,
@@ -91,6 +92,10 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
public topHoldingsMap: {
[name: string]: { name: string; value: number };
};
+ public tagHoldings: Holding[];
+ public tagHoldingsMap: {
+ [name: string]: { name: string; value: number };
+ };
public totalValueInEtf = 0;
public UNKNOWN_KEY = UNKNOWN_KEY;
public user: User;
@@ -282,6 +287,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
}
};
this.topHoldingsMap = {};
+ this.tagHoldingsMap = {};
}
private initializeAllocationsData() {
@@ -417,6 +423,22 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
}
}
+ if (position.tags.length > 0) {
+ for (const tag of position.tags) {
+ const { name } = tag;
+
+ if (this.tagHoldingsMap[name]?.value) {
+ this.tagHoldingsMap[name].value +=
+ position.valueInBaseCurrency ?? 0;
+ } else {
+ this.tagHoldingsMap[name] = {
+ name,
+ value: position.valueInBaseCurrency ?? 0
+ };
+ }
+ }
+ }
+
if (position.sectors.length > 0) {
for (const sector of position.sectors) {
const { name, weight } = sector;
@@ -480,6 +502,29 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
};
}
+ this.tagHoldings = Object.values(this.tagHoldingsMap)
+ .map(({ name, value }) => {
+ if (this.hasImpersonationId || this.user.settings.isRestrictedView) {
+ return {
+ name,
+ allocationInPercentage: value,
+ valueInBaseCurrency: null
+ };
+ }
+
+ return {
+ name,
+ allocationInPercentage:
+ this.portfolioDetails.summary.currentValueInBaseCurrency > 0
+ ? value / this.portfolioDetails.summary.currentValueInBaseCurrency
+ : 0,
+ valueInBaseCurrency: value
+ };
+ })
+ .sort((a, b) => {
+ return b.allocationInPercentage - a.allocationInPercentage;
+ });
+
this.topHoldings = Object.values(this.topHoldingsMap)
.map(({ name, value }) => {
if (this.hasImpersonationId || this.user.settings.isRestrictedView) {
diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.html b/apps/client/src/app/pages/portfolio/allocations/allocations-page.html
index f2dff76f3..49f53a02c 100644
--- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.html
+++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.html
@@ -327,6 +327,24 @@
'd-none': !user?.settings?.isExperimentalFeatures
}"
>
+
+
+
+ By Tag Holding
+ @if (user?.subscription?.type === 'Basic') {
+
+ }
+
+
+
+
+
+