From 0d1878148bfce5e66af4ca34a2ab12c6be61fcfa Mon Sep 17 00:00:00 2001 From: Dan Date: Sat, 1 Feb 2025 10:13:58 +0100 Subject: [PATCH 1/3] Concat Tags from SymbolProfile as well --- apps/api/src/app/portfolio/portfolio.controller.ts | 2 ++ apps/api/src/app/portfolio/portfolio.service.ts | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) 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 From d4ee540c7a34753eca12474bedd5202cb2b3fba2 Mon Sep 17 00:00:00 2001 From: Dan Date: Sat, 1 Feb 2025 10:40:23 +0100 Subject: [PATCH 2/3] Add Tag overview to allocation page --- .../allocations/allocations-page.component.ts | 44 +++++++++++++++++++ .../allocations/allocations-page.html | 18 ++++++++ 2 files changed, 62 insertions(+) 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..233c4bd30 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 @@ -91,6 +91,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 +286,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { } }; this.topHoldingsMap = {}; + this.tagHoldingsMap = {}; } private initializeAllocationsData() { @@ -417,6 +422,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 +501,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') { + + } + + + + + + From fb98a5d5417778cd5fcc6ce6633b26b3f28175ae Mon Sep 17 00:00:00 2001 From: Dan Date: Sat, 1 Feb 2025 10:58:29 +0100 Subject: [PATCH 3/3] Fix Frontend --- .../pages/portfolio/allocations/allocations-page.component.ts | 1 + 1 file changed, 1 insertion(+) 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 233c4bd30..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,