Browse Source

Task/remove deprecated attributes from portfolio position interface (#6950)

* Remove deprecated attributes

* Update changelog
pull/6962/head^2
Thomas Kaul 3 days ago
committed by GitHub
parent
commit
ab6fdf6c4f
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 44
      apps/api/src/app/portfolio/portfolio.controller.ts
  3. 34
      apps/api/src/app/portfolio/portfolio.service.ts
  4. 2
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  5. 3
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
  6. 50
      apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
  7. 16
      apps/client/src/app/pages/portfolio/analysis/analysis-page.html
  8. 5
      apps/client/src/app/pages/public/public-page.component.ts
  9. 47
      libs/common/src/lib/interfaces/portfolio-position.interface.ts
  10. 37
      libs/ui/src/lib/assistant/assistant.component.ts
  11. 200
      libs/ui/src/lib/mocks/holdings.ts
  12. 9
      libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html
  13. 3
      libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts

1
CHANGELOG.md

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Extended the countries mapping in the data enhancer for asset profile data via _Trackinsight_ - Extended the countries mapping in the data enhancer for asset profile data via _Trackinsight_
- Removed the deprecated attributes (`assetClass`, `assetClassLabel`, `assetSubClass`, `assetSubClassLabel`, `countries`, `currency`, `dataSource`, `holdings`, `name`, `sectors`, `symbol` and `url`) from the holdings of the portfolio details endpoint response
## 3.6.0 - 2026-05-28 ## 3.6.0 - 2026-05-28

44
apps/api/src/app/portfolio/portfolio.controller.ts

@ -144,10 +144,10 @@ export class PortfolioController {
.reduce((a, b) => a + b, 0); .reduce((a, b) => a + b, 0);
const totalValue = Object.values(holdings) const totalValue = Object.values(holdings)
.filter(({ assetClass, assetSubClass }) => { .filter(({ assetProfile }) => {
return ( return (
assetClass !== AssetClass.LIQUIDITY && assetProfile.assetClass !== AssetClass.LIQUIDITY &&
assetSubClass !== AssetSubClass.CASH assetProfile.assetSubClass !== AssetSubClass.CASH
); );
}) })
.map(({ valueInBaseCurrency }) => { .map(({ valueInBaseCurrency }) => {
@ -217,37 +217,41 @@ export class PortfolioController {
for (const [symbol, portfolioPosition] of Object.entries(holdings)) { for (const [symbol, portfolioPosition] of Object.entries(holdings)) {
holdings[symbol] = { holdings[symbol] = {
...portfolioPosition, ...portfolioPosition,
assetClass:
hasDetails || portfolioPosition.assetClass === AssetClass.LIQUIDITY
? portfolioPosition.assetClass
: undefined,
assetProfile: { assetProfile: {
...portfolioPosition.assetProfile, ...portfolioPosition.assetProfile,
assetClass:
hasDetails ||
portfolioPosition.assetProfile.assetClass === AssetClass.LIQUIDITY
? portfolioPosition.assetProfile.assetClass
: undefined,
assetClassLabel:
hasDetails ||
portfolioPosition.assetProfile.assetClass === AssetClass.LIQUIDITY
? portfolioPosition.assetProfile.assetClassLabel
: undefined,
assetSubClass:
hasDetails ||
portfolioPosition.assetProfile.assetSubClass === AssetSubClass.CASH
? portfolioPosition.assetProfile.assetSubClass
: undefined,
assetSubClassLabel:
hasDetails ||
portfolioPosition.assetProfile.assetSubClass === AssetSubClass.CASH
? portfolioPosition.assetProfile.assetSubClassLabel
: undefined,
...(hasDetails ...(hasDetails
? {} ? {}
: { : {
assetClass: undefined,
assetClassLabel: undefined,
assetSubClass: undefined,
assetSubClassLabel: undefined,
countries: [], countries: [],
currency: undefined, currency: undefined,
holdings: [], holdings: [],
sectors: [] sectors: []
}) })
}, },
assetSubClass:
hasDetails || portfolioPosition.assetSubClass === AssetSubClass.CASH
? portfolioPosition.assetSubClass
: undefined,
countries: hasDetails ? portfolioPosition.countries : [],
currency: hasDetails ? portfolioPosition.currency : undefined,
holdings: hasDetails ? portfolioPosition.holdings : [],
markets: hasDetails ? portfolioPosition.markets : undefined, markets: hasDetails ? portfolioPosition.markets : undefined,
marketsAdvanced: hasDetails marketsAdvanced: hasDetails
? portfolioPosition.marketsAdvanced ? portfolioPosition.marketsAdvanced
: undefined, : undefined
sectors: hasDetails ? portfolioPosition.sectors : []
}; };
} }

34
apps/api/src/app/portfolio/portfolio.service.ts

@ -584,7 +584,6 @@ export class PortfolioService {
for (const { for (const {
activitiesCount, activitiesCount,
currency,
dataSource, dataSource,
dateOfFirstActivity, dateOfFirstActivity,
dividend, dividend,
@ -638,16 +637,13 @@ export class PortfolioService {
holdings[symbol] = { holdings[symbol] = {
activitiesCount, activitiesCount,
currency,
markets, markets,
marketsAdvanced, marketsAdvanced,
marketPrice, marketPrice,
symbol,
tags, tags,
allocationInPercentage: filteredValueInBaseCurrency.eq(0) allocationInPercentage: filteredValueInBaseCurrency.eq(0)
? 0 ? 0
: valueInBaseCurrency.div(filteredValueInBaseCurrency).toNumber(), : valueInBaseCurrency.div(filteredValueInBaseCurrency).toNumber(),
assetClass: assetProfile.assetClass,
assetProfile: { assetProfile: {
assetClass: assetProfile.assetClass, assetClass: assetProfile.assetClass,
assetSubClass: assetProfile.assetSubClass, assetSubClass: assetProfile.assetSubClass,
@ -670,9 +666,6 @@ export class PortfolioService {
symbol: assetProfile.symbol, symbol: assetProfile.symbol,
url: assetProfile.url url: assetProfile.url
}, },
assetSubClass: assetProfile.assetSubClass,
countries: assetProfile.countries,
dataSource: assetProfile.dataSource,
dateOfFirstActivity: parseDate(dateOfFirstActivity), dateOfFirstActivity: parseDate(dateOfFirstActivity),
dividend: dividend?.toNumber() ?? 0, dividend: dividend?.toNumber() ?? 0,
grossPerformance: grossPerformance?.toNumber() ?? 0, grossPerformance: grossPerformance?.toNumber() ?? 0,
@ -681,19 +674,7 @@ export class PortfolioService {
grossPerformancePercentageWithCurrencyEffect?.toNumber() ?? 0, grossPerformancePercentageWithCurrencyEffect?.toNumber() ?? 0,
grossPerformanceWithCurrencyEffect: grossPerformanceWithCurrencyEffect:
grossPerformanceWithCurrencyEffect?.toNumber() ?? 0, grossPerformanceWithCurrencyEffect?.toNumber() ?? 0,
holdings: assetProfile.holdings.map(
({ allocationInPercentage, name }) => {
return {
allocationInPercentage,
name,
valueInBaseCurrency: valueInBaseCurrency
.mul(allocationInPercentage)
.toNumber()
};
}
),
investment: investment.toNumber(), investment: investment.toNumber(),
name: assetProfile.name,
netPerformance: netPerformance?.toNumber() ?? 0, netPerformance: netPerformance?.toNumber() ?? 0,
netPerformancePercent: netPerformancePercentage?.toNumber() ?? 0, netPerformancePercent: netPerformancePercentage?.toNumber() ?? 0,
netPerformancePercentWithCurrencyEffect: netPerformancePercentWithCurrencyEffect:
@ -703,8 +684,6 @@ export class PortfolioService {
netPerformanceWithCurrencyEffect: netPerformanceWithCurrencyEffect:
netPerformanceWithCurrencyEffectMap?.[dateRange]?.toNumber() ?? 0, netPerformanceWithCurrencyEffectMap?.[dateRange]?.toNumber() ?? 0,
quantity: quantity.toNumber(), quantity: quantity.toNumber(),
sectors: assetProfile.sectors,
url: assetProfile.url,
valueInBaseCurrency: valueInBaseCurrency.toNumber() valueInBaseCurrency: valueInBaseCurrency.toNumber()
}; };
} }
@ -1472,8 +1451,8 @@ export class PortfolioService {
for (const [, position] of Object.entries(holdings)) { for (const [, position] of Object.entries(holdings)) {
const value = position.valueInBaseCurrency; const value = position.valueInBaseCurrency;
if (position.assetClass !== AssetClass.LIQUIDITY) { if (position.assetProfile.assetClass !== AssetClass.LIQUIDITY) {
if (position.countries.length > 0) { if (position.assetProfile.countries.length > 0) {
markets.developedMarkets.valueInBaseCurrency += markets.developedMarkets.valueInBaseCurrency +=
position.markets.developedMarkets * value; position.markets.developedMarkets * value;
markets.emergingMarkets.valueInBaseCurrency += markets.emergingMarkets.valueInBaseCurrency +=
@ -1719,11 +1698,8 @@ export class PortfolioService {
currency: string; currency: string;
}): PortfolioPosition { }): PortfolioPosition {
return { return {
currency,
activitiesCount: 0, activitiesCount: 0,
allocationInPercentage: 0, allocationInPercentage: 0,
assetClass: AssetClass.LIQUIDITY,
assetSubClass: AssetSubClass.CASH,
assetProfile: { assetProfile: {
currency, currency,
assetClass: AssetClass.LIQUIDITY, assetClass: AssetClass.LIQUIDITY,
@ -1735,25 +1711,19 @@ export class PortfolioService {
sectors: [], sectors: [],
symbol: currency symbol: currency
}, },
countries: [],
dataSource: undefined,
dateOfFirstActivity: undefined, dateOfFirstActivity: undefined,
dividend: 0, dividend: 0,
grossPerformance: 0, grossPerformance: 0,
grossPerformancePercent: 0, grossPerformancePercent: 0,
grossPerformancePercentWithCurrencyEffect: 0, grossPerformancePercentWithCurrencyEffect: 0,
grossPerformanceWithCurrencyEffect: 0, grossPerformanceWithCurrencyEffect: 0,
holdings: [],
investment: balance, investment: balance,
marketPrice: 0, marketPrice: 0,
name: currency,
netPerformance: 0, netPerformance: 0,
netPerformancePercent: 0, netPerformancePercent: 0,
netPerformancePercentWithCurrencyEffect: 0, netPerformancePercentWithCurrencyEffect: 0,
netPerformanceWithCurrencyEffect: 0, netPerformanceWithCurrencyEffect: 0,
quantity: 0, quantity: 0,
sectors: [],
symbol: currency,
tags: [], tags: [],
valueInBaseCurrency: balance valueInBaseCurrency: balance
}; };

2
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts

@ -139,7 +139,7 @@ export class GfCreateOrUpdateActivityDialogComponent {
return !['CASH'].includes(assetProfile.assetSubClass); return !['CASH'].includes(assetProfile.assetSubClass);
}) })
.sort((a, b) => { .sort((a, b) => {
return a.name?.localeCompare(b.name); return a.assetProfile.name?.localeCompare(b.assetProfile.name);
}) })
.map(({ assetProfile }) => { .map(({ assetProfile }) => {
return { return {

3
apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts

@ -226,7 +226,8 @@ export class GfImportActivitiesDialogComponent {
this.assetProfileForm.controls.assetProfileIdentifier.disable(); this.assetProfileForm.controls.assetProfileIdentifier.disable();
const { dataSource, symbol } = const { dataSource, symbol } =
this.assetProfileForm.controls.assetProfileIdentifier.value ?? {}; this.assetProfileForm.controls.assetProfileIdentifier.value
?.assetProfile ?? {};
if (!dataSource || !symbol) { if (!dataSource || !symbol) {
return; return;

50
apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts

@ -73,15 +73,14 @@ export class GfAllocationsPageComponent implements OnInit {
public hasImpersonationId: boolean; public hasImpersonationId: boolean;
public holdings: { public holdings: {
[symbol: string]: Pick< [symbol: string]: Pick<
PortfolioPosition, PortfolioPosition['assetProfile'],
| 'assetClass' | 'assetClass'
| 'assetClassLabel' | 'assetClassLabel'
| 'assetSubClass' | 'assetSubClass'
| 'assetSubClassLabel' | 'assetSubClassLabel'
| 'currency' | 'currency'
| 'exchange'
| 'name' | 'name'
> & { etfProvider: string; value: number }; > & { etfProvider: string; exchange?: string; value: number };
}; };
public isLoading = false; public isLoading = false;
public markets: { public markets: {
@ -206,7 +205,7 @@ export class GfAllocationsPageComponent implements OnInit {
assetSubClass, assetSubClass,
name name
}: { }: {
assetSubClass: PortfolioPosition['assetSubClass']; assetSubClass: PortfolioPosition['assetProfile']['assetSubClass'];
name: string; name: string;
}) { }) {
if (assetSubClass === 'ETF') { if (assetSubClass === 'ETF') {
@ -333,24 +332,27 @@ export class GfAllocationsPageComponent implements OnInit {
this.holdings[symbol] = { this.holdings[symbol] = {
value, value,
assetClass: position.assetClass || (UNKNOWN_KEY as AssetClass), assetClass:
assetClassLabel: position.assetClassLabel || UNKNOWN_KEY, position.assetProfile.assetClass || (UNKNOWN_KEY as AssetClass),
assetSubClass: position.assetSubClass || (UNKNOWN_KEY as AssetSubClass), assetClassLabel: position.assetProfile.assetClassLabel || UNKNOWN_KEY,
assetSubClassLabel: position.assetSubClassLabel || UNKNOWN_KEY, assetSubClass:
currency: position.currency, position.assetProfile.assetSubClass || (UNKNOWN_KEY as AssetSubClass),
assetSubClassLabel:
position.assetProfile.assetSubClassLabel || UNKNOWN_KEY,
currency: position.assetProfile.currency,
etfProvider: this.extractEtfProvider({ etfProvider: this.extractEtfProvider({
assetSubClass: position.assetSubClass, assetSubClass: position.assetProfile.assetSubClass,
name: position.name name: position.assetProfile.name
}), }),
exchange: position.exchange, exchange: position.exchange,
name: position.name name: position.assetProfile.name
}; };
if (position.assetClass !== AssetClass.LIQUIDITY) { if (position.assetProfile.assetClass !== AssetClass.LIQUIDITY) {
// Prepare analysis data by continents, countries, holdings and sectors except for liquidity // Prepare analysis data by continents, countries, holdings and sectors except for liquidity
if (position.countries.length > 0) { if (position.assetProfile.countries.length > 0) {
for (const country of position.countries) { for (const country of position.assetProfile.countries) {
const { code, continent, name, weight } = country; const { code, continent, name, weight } = country;
if (this.continents[continent]?.value) { if (this.continents[continent]?.value) {
@ -401,12 +403,12 @@ export class GfAllocationsPageComponent implements OnInit {
: this.portfolioDetails.holdings[symbol].valueInPercentage; : this.portfolioDetails.holdings[symbol].valueInPercentage;
} }
if (position.holdings.length > 0) { if (position.assetProfile.holdings.length > 0) {
for (const { for (const {
allocationInPercentage, allocationInPercentage,
name, name,
valueInBaseCurrency valueInBaseCurrency
} of position.holdings) { } of position.assetProfile.holdings) {
const normalizedAssetName = this.normalizeAssetName(name); const normalizedAssetName = this.normalizeAssetName(name);
if (this.topHoldingsMap[normalizedAssetName]?.value) { if (this.topHoldingsMap[normalizedAssetName]?.value) {
@ -428,8 +430,8 @@ export class GfAllocationsPageComponent implements OnInit {
} }
} }
if (position.sectors.length > 0) { if (position.assetProfile.sectors.length > 0) {
for (const sector of position.sectors) { for (const sector of position.assetProfile.sectors) {
const { name, weight } = sector; const { name, weight } = sector;
if (this.sectors[name]?.value) { if (this.sectors[name]?.value) {
@ -463,8 +465,8 @@ export class GfAllocationsPageComponent implements OnInit {
} }
this.symbols[prettifySymbol(symbol)] = { this.symbols[prettifySymbol(symbol)] = {
dataSource: position.dataSource, dataSource: position.assetProfile.dataSource,
name: position.name, name: position.assetProfile.name,
symbol: prettifySymbol(symbol), symbol: prettifySymbol(symbol),
value: isNumber(position.valueInBaseCurrency) value: isNumber(position.valueInBaseCurrency)
? position.valueInBaseCurrency ? position.valueInBaseCurrency
@ -517,8 +519,8 @@ export class GfAllocationsPageComponent implements OnInit {
this.totalValueInEtf > 0 ? value / this.totalValueInEtf : 0, this.totalValueInEtf > 0 ? value / this.totalValueInEtf : 0,
parents: Object.entries(this.portfolioDetails.holdings) parents: Object.entries(this.portfolioDetails.holdings)
.map(([symbol, holding]) => { .map(([symbol, holding]) => {
if (holding.holdings.length > 0) { if (holding.assetProfile.holdings.length > 0) {
const currentParentHolding = holding.holdings.find( const currentParentHolding = holding.assetProfile.holdings.find(
(parentHolding) => { (parentHolding) => {
return ( return (
this.normalizeAssetName(parentHolding.name) === this.normalizeAssetName(parentHolding.name) ===
@ -531,7 +533,7 @@ export class GfAllocationsPageComponent implements OnInit {
? { ? {
allocationInPercentage: allocationInPercentage:
currentParentHolding.valueInBaseCurrency / value, currentParentHolding.valueInBaseCurrency / value,
name: holding.name, name: holding.assetProfile.name,
position: holding, position: holding,
symbol: prettifySymbol(symbol), symbol: prettifySymbol(symbol),
valueInBaseCurrency: valueInBaseCurrency:

16
apps/client/src/app/pages/portfolio/analysis/analysis-page.html

@ -310,13 +310,15 @@
<a <a
class="d-flex" class="d-flex"
[queryParams]="{ [queryParams]="{
dataSource: holding.dataSource, dataSource: holding.assetProfile.dataSource,
holdingDetailDialog: true, holdingDetailDialog: true,
symbol: holding.symbol symbol: holding.assetProfile.symbol
}" }"
[routerLink]="[]" [routerLink]="[]"
> >
<div class="flex-grow-1 mr-2">{{ holding.name }}</div> <div class="flex-grow-1 mr-2">
{{ holding.assetProfile.name }}
</div>
<div class="d-flex justify-content-end"> <div class="d-flex justify-content-end">
<gf-value <gf-value
class="justify-content-end" class="justify-content-end"
@ -359,13 +361,15 @@
<a <a
class="d-flex" class="d-flex"
[queryParams]="{ [queryParams]="{
dataSource: holding.dataSource, dataSource: holding.assetProfile.dataSource,
holdingDetailDialog: true, holdingDetailDialog: true,
symbol: holding.symbol symbol: holding.assetProfile.symbol
}" }"
[routerLink]="[]" [routerLink]="[]"
> >
<div class="flex-grow-1 mr-2">{{ holding.name }}</div> <div class="flex-grow-1 mr-2">
{{ holding.assetProfile.name }}
</div>
<div class="d-flex justify-content-end"> <div class="d-flex justify-content-end">
<gf-value <gf-value
class="justify-content-end" class="justify-content-end"

5
apps/client/src/app/pages/public/public-page.component.ts

@ -74,7 +74,10 @@ export class GfPublicPageComponent implements OnInit {
}; };
protected readonly pageSize = Number.MAX_SAFE_INTEGER; protected readonly pageSize = Number.MAX_SAFE_INTEGER;
protected positions: { protected positions: {
[symbol: string]: Pick<PortfolioPosition, 'currency' | 'name'> & { [symbol: string]: Pick<
PortfolioPosition['assetProfile'],
'currency' | 'name'
> & {
value: number; value: number;
}; };
}; };

47
libs/common/src/lib/interfaces/portfolio-position.interface.ts

@ -1,22 +1,12 @@
import { Market, MarketAdvanced } from '@ghostfolio/common/types'; import { Market, MarketAdvanced } from '@ghostfolio/common/types';
import { AssetClass, AssetSubClass, DataSource, Tag } from '@prisma/client'; import { Tag } from '@prisma/client';
import { Country } from './country.interface';
import { EnhancedSymbolProfile } from './enhanced-symbol-profile.interface'; import { EnhancedSymbolProfile } from './enhanced-symbol-profile.interface';
import { Holding } from './holding.interface';
import { Sector } from './sector.interface';
export interface PortfolioPosition { export interface PortfolioPosition {
activitiesCount: number; activitiesCount: number;
allocationInPercentage: number; allocationInPercentage: number;
/** @deprecated */
assetClass?: AssetClass;
/** @deprecated */
assetClassLabel?: string;
assetProfile: Pick< assetProfile: Pick<
EnhancedSymbolProfile, EnhancedSymbolProfile,
| 'assetClass' | 'assetClass'
@ -33,22 +23,6 @@ export interface PortfolioPosition {
assetClassLabel?: string; assetClassLabel?: string;
assetSubClassLabel?: string; assetSubClassLabel?: string;
}; };
/** @deprecated */
assetSubClass?: AssetSubClass;
/** @deprecated */
assetSubClassLabel?: string;
/** @deprecated */
countries: Country[];
/** @deprecated */
currency: string;
/** @deprecated */
dataSource: DataSource;
dateOfFirstActivity: Date; dateOfFirstActivity: Date;
dividend: number; dividend: number;
exchange?: string; exchange?: string;
@ -56,38 +30,19 @@ export interface PortfolioPosition {
grossPerformancePercent: number; grossPerformancePercent: number;
grossPerformancePercentWithCurrencyEffect: number; grossPerformancePercentWithCurrencyEffect: number;
grossPerformanceWithCurrencyEffect: number; grossPerformanceWithCurrencyEffect: number;
/** @deprecated */
holdings: Holding[];
investment: number; investment: number;
marketChange?: number; marketChange?: number;
marketChangePercent?: number; marketChangePercent?: number;
marketPrice: number; marketPrice: number;
markets?: { [key in Market]: number }; markets?: { [key in Market]: number };
marketsAdvanced?: { [key in MarketAdvanced]: number }; marketsAdvanced?: { [key in MarketAdvanced]: number };
/** @deprecated */
name: string;
netPerformance: number; netPerformance: number;
netPerformancePercent: number; netPerformancePercent: number;
netPerformancePercentWithCurrencyEffect: number; netPerformancePercentWithCurrencyEffect: number;
netPerformanceWithCurrencyEffect: number; netPerformanceWithCurrencyEffect: number;
quantity: number; quantity: number;
/** @deprecated */
sectors: Sector[];
/** @deprecated */
symbol: string;
tags?: Tag[]; tags?: Tag[];
type?: string; type?: string;
/** @deprecated */
url?: string;
valueInBaseCurrency?: number; valueInBaseCurrency?: number;
valueInPercentage?: number; valueInPercentage?: number;
} }

37
libs/ui/src/lib/assistant/assistant.component.ts

@ -504,11 +504,16 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
.pipe(takeUntilDestroyed(this.destroyRef)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ holdings }) => { .subscribe(({ holdings }) => {
this.holdings = holdings this.holdings = holdings
.filter(({ assetSubClass }) => { .filter(({ assetProfile }) => {
return assetSubClass && !['CASH'].includes(assetSubClass); return (
assetProfile.assetSubClass &&
!['CASH'].includes(assetProfile.assetSubClass)
);
}) })
.sort((a, b) => { .sort((a, b) => {
return a.name?.localeCompare(b.name); return (a.assetProfile.name ?? '').localeCompare(
b.assetProfile.name ?? ''
);
}); });
this.setPortfolioFilterFormValues(); this.setPortfolioFilterFormValues();
@ -530,11 +535,11 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
type: 'ASSET_CLASS' type: 'ASSET_CLASS'
}, },
{ {
id: filterValue?.holding?.dataSource ?? '', id: filterValue?.holding?.assetProfile?.dataSource ?? '',
type: 'DATA_SOURCE' type: 'DATA_SOURCE'
}, },
{ {
id: filterValue?.holding?.symbol ?? '', id: filterValue?.holding?.assetProfile?.symbol ?? '',
type: 'SYMBOL' type: 'SYMBOL'
}, },
{ {
@ -718,18 +723,16 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
return EMPTY; return EMPTY;
}), }),
map(({ holdings }) => { map(({ holdings }) => {
return holdings.map( return holdings.map(({ assetProfile }) => {
({ assetSubClass, currency, dataSource, name, symbol }) => {
return { return {
currency, assetSubClassString: translate(assetProfile.assetSubClass ?? ''),
dataSource, currency: assetProfile.currency ?? '',
name, dataSource: assetProfile.dataSource,
symbol, mode: SearchMode.HOLDING as const,
assetSubClassString: translate(assetSubClass ?? ''), name: assetProfile.name ?? '',
mode: SearchMode.HOLDING as const symbol: assetProfile.symbol
}; };
} });
);
}), }),
takeUntilDestroyed(this.destroyRef) takeUntilDestroyed(this.destroyRef)
); );
@ -777,8 +780,8 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
return ( return (
!!(dataSource && symbol) && !!(dataSource && symbol) &&
getAssetProfileIdentifier({ getAssetProfileIdentifier({
dataSource: holding.dataSource, dataSource: holding.assetProfile.dataSource,
symbol: holding.symbol symbol: holding.assetProfile.symbol
}) === getAssetProfileIdentifier({ dataSource, symbol }) }) === getAssetProfileIdentifier({ dataSource, symbol })
); );
}); });

200
libs/ui/src/lib/mocks/holdings.ts

@ -4,11 +4,11 @@ export const holdings: PortfolioPosition[] = [
{ {
activitiesCount: 1, activitiesCount: 1,
allocationInPercentage: 0.042990776363386086, allocationInPercentage: 0.042990776363386086,
assetClass: 'EQUITY',
assetClassLabel: 'Equity',
assetProfile: { assetProfile: {
assetClass: 'EQUITY', assetClass: 'EQUITY',
assetClassLabel: 'Equity',
assetSubClass: 'STOCK', assetSubClass: 'STOCK',
assetSubClassLabel: 'Stock',
countries: [ countries: [
{ {
code: 'US', code: 'US',
@ -20,60 +20,40 @@ export const holdings: PortfolioPosition[] = [
currency: 'USD', currency: 'USD',
dataSource: 'YAHOO', dataSource: 'YAHOO',
holdings: [], holdings: [],
name: 'Apple Inc',
sectors: [ sectors: [
{ {
name: 'Technology', name: 'Technology',
weight: 1 weight: 1
} }
], ],
symbol: 'AAPL' symbol: 'AAPL',
url: 'https://www.apple.com'
}, },
assetSubClass: 'STOCK',
assetSubClassLabel: 'Stock',
countries: [
{
code: 'US',
continent: 'North America',
name: 'United States',
weight: 1
}
],
currency: 'USD',
dataSource: 'YAHOO',
dateOfFirstActivity: new Date('2021-12-01T00:00:00.000Z'), dateOfFirstActivity: new Date('2021-12-01T00:00:00.000Z'),
dividend: 0, dividend: 0,
grossPerformance: 3856, grossPerformance: 3856,
grossPerformancePercent: 0.46047289228564603, grossPerformancePercent: 0.46047289228564603,
grossPerformancePercentWithCurrencyEffect: 0.46047289228564603, grossPerformancePercentWithCurrencyEffect: 0.46047289228564603,
grossPerformanceWithCurrencyEffect: 3856, grossPerformanceWithCurrencyEffect: 3856,
holdings: [],
investment: 8374, investment: 8374,
marketPrice: 244.6, marketPrice: 244.6,
name: 'Apple Inc',
netPerformance: 3855, netPerformance: 3855,
netPerformancePercent: 0.460353475041796, netPerformancePercent: 0.460353475041796,
netPerformancePercentWithCurrencyEffect: 0.036440677966101696, netPerformancePercentWithCurrencyEffect: 0.036440677966101696,
netPerformanceWithCurrencyEffect: 430, netPerformanceWithCurrencyEffect: 430,
quantity: 50, quantity: 50,
sectors: [
{
name: 'Technology',
weight: 1
}
],
symbol: 'AAPL',
tags: [], tags: [],
url: 'https://www.apple.com',
valueInBaseCurrency: 12230 valueInBaseCurrency: 12230
}, },
{ {
activitiesCount: 2, activitiesCount: 2,
allocationInPercentage: 0.02377401948293552, allocationInPercentage: 0.02377401948293552,
assetClass: 'EQUITY',
assetClassLabel: 'Equity',
assetProfile: { assetProfile: {
assetClass: 'EQUITY', assetClass: 'EQUITY',
assetClassLabel: 'Equity',
assetSubClass: 'STOCK', assetSubClass: 'STOCK',
assetSubClassLabel: 'Stock',
countries: [ countries: [
{ {
code: 'DE', code: 'DE',
@ -85,60 +65,40 @@ export const holdings: PortfolioPosition[] = [
currency: 'EUR', currency: 'EUR',
dataSource: 'YAHOO', dataSource: 'YAHOO',
holdings: [], holdings: [],
name: 'Allianz SE',
sectors: [ sectors: [
{ {
name: 'Financial Services', name: 'Financial Services',
weight: 1 weight: 1
} }
], ],
symbol: 'ALV.DE' symbol: 'ALV.DE',
url: 'https://www.allianz.com'
}, },
assetSubClass: 'STOCK',
assetSubClassLabel: 'Stock',
countries: [
{
code: 'DE',
continent: 'Europe',
name: 'Germany',
weight: 1
}
],
currency: 'EUR',
dataSource: 'YAHOO',
dateOfFirstActivity: new Date('2021-04-23T00:00:00.000Z'), dateOfFirstActivity: new Date('2021-04-23T00:00:00.000Z'),
dividend: 192, dividend: 192,
grossPerformance: 2226.700251889169, grossPerformance: 2226.700251889169,
grossPerformancePercent: 0.49083842309827874, grossPerformancePercent: 0.49083842309827874,
grossPerformancePercentWithCurrencyEffect: 0.29306136948826367, grossPerformancePercentWithCurrencyEffect: 0.29306136948826367,
grossPerformanceWithCurrencyEffect: 1532.8272791336772, grossPerformanceWithCurrencyEffect: 1532.8272791336772,
holdings: [],
investment: 4536.523929471033, investment: 4536.523929471033,
marketPrice: 322.2, marketPrice: 322.2,
name: 'Allianz SE',
netPerformance: 2222.2921914357685, netPerformance: 2222.2921914357685,
netPerformancePercent: 0.48986674069961134, netPerformancePercent: 0.48986674069961134,
netPerformancePercentWithCurrencyEffect: 0.034489367670592026, netPerformancePercentWithCurrencyEffect: 0.034489367670592026,
netPerformanceWithCurrencyEffect: 225.48257403052068, netPerformanceWithCurrencyEffect: 225.48257403052068,
quantity: 20, quantity: 20,
sectors: [
{
name: 'Financial Services',
weight: 1
}
],
symbol: 'ALV.DE',
tags: [], tags: [],
url: 'https://www.allianz.com',
valueInBaseCurrency: 6763.224181360202 valueInBaseCurrency: 6763.224181360202
}, },
{ {
activitiesCount: 1, activitiesCount: 1,
allocationInPercentage: 0.08038536990007467, allocationInPercentage: 0.08038536990007467,
assetClass: 'EQUITY',
assetClassLabel: 'Equity',
assetProfile: { assetProfile: {
assetClass: 'EQUITY', assetClass: 'EQUITY',
assetClassLabel: 'Equity',
assetSubClass: 'STOCK', assetSubClass: 'STOCK',
assetSubClassLabel: 'Stock',
countries: [ countries: [
{ {
code: 'US', code: 'US',
@ -150,101 +110,73 @@ export const holdings: PortfolioPosition[] = [
currency: 'USD', currency: 'USD',
dataSource: 'YAHOO', dataSource: 'YAHOO',
holdings: [], holdings: [],
name: 'Amazon.com, Inc.',
sectors: [ sectors: [
{ {
name: 'Consumer Discretionary', name: 'Consumer Discretionary',
weight: 1 weight: 1
} }
], ],
symbol: 'AMZN' symbol: 'AMZN',
url: 'https://www.aboutamazon.com'
}, },
assetSubClass: 'STOCK',
assetSubClassLabel: 'Stock',
countries: [
{
code: 'US',
continent: 'North America',
name: 'United States',
weight: 1
}
],
currency: 'USD',
dataSource: 'YAHOO',
dateOfFirstActivity: new Date('2018-10-01T00:00:00.000Z'), dateOfFirstActivity: new Date('2018-10-01T00:00:00.000Z'),
dividend: 0, dividend: 0,
grossPerformance: 12758.05, grossPerformance: 12758.05,
grossPerformancePercent: 1.2619300787837724, grossPerformancePercent: 1.2619300787837724,
grossPerformancePercentWithCurrencyEffect: 1.2619300787837724, grossPerformancePercentWithCurrencyEffect: 1.2619300787837724,
grossPerformanceWithCurrencyEffect: 12758.05, grossPerformanceWithCurrencyEffect: 12758.05,
holdings: [],
investment: 10109.95, investment: 10109.95,
marketPrice: 228.68, marketPrice: 228.68,
name: 'Amazon.com, Inc.',
netPerformance: 12677.26, netPerformance: 12677.26,
netPerformancePercent: 1.253938941339967, netPerformancePercent: 1.253938941339967,
netPerformancePercentWithCurrencyEffect: -0.037866008722316276, netPerformancePercentWithCurrencyEffect: -0.037866008722316276,
netPerformanceWithCurrencyEffect: -899.99926757812, netPerformanceWithCurrencyEffect: -899.99926757812,
quantity: 100, quantity: 100,
sectors: [
{
name: 'Consumer Discretionary',
weight: 1
}
],
symbol: 'AMZN',
tags: [], tags: [],
url: 'https://www.aboutamazon.com',
valueInBaseCurrency: 22868 valueInBaseCurrency: 22868
}, },
{ {
activitiesCount: 1, activitiesCount: 1,
allocationInPercentage: 0.19216416482928922, allocationInPercentage: 0.19216416482928922,
assetClass: 'LIQUIDITY',
assetClassLabel: 'Liquidity',
assetProfile: { assetProfile: {
assetClass: 'LIQUIDITY', assetClass: 'LIQUIDITY',
assetSubClass: 'CASH', assetClassLabel: 'Liquidity',
assetSubClass: 'CRYPTOCURRENCY',
assetSubClassLabel: 'Cryptocurrency',
countries: [], countries: [],
currency: 'USD', currency: 'USD',
dataSource: 'COINGECKO', dataSource: 'COINGECKO',
holdings: [], holdings: [],
name: 'Bitcoin',
sectors: [], sectors: [],
symbol: 'bitcoin' symbol: 'bitcoin',
url: undefined
}, },
assetSubClass: 'CRYPTOCURRENCY',
assetSubClassLabel: 'Cryptocurrency',
countries: [],
currency: 'USD',
dataSource: 'COINGECKO',
dateOfFirstActivity: new Date('2017-08-16T00:00:00.000Z'), dateOfFirstActivity: new Date('2017-08-16T00:00:00.000Z'),
dividend: 0, dividend: 0,
grossPerformance: 52666.7898248, grossPerformance: 52666.7898248,
grossPerformancePercent: 26.333394912400003, grossPerformancePercent: 26.333394912400003,
grossPerformancePercentWithCurrencyEffect: 26.333394912400003, grossPerformancePercentWithCurrencyEffect: 26.333394912400003,
grossPerformanceWithCurrencyEffect: 52666.7898248, grossPerformanceWithCurrencyEffect: 52666.7898248,
holdings: [],
investment: 1999.9999999999998, investment: 1999.9999999999998,
marketPrice: 97364, marketPrice: 97364,
name: 'Bitcoin',
netPerformance: 52636.8898248, netPerformance: 52636.8898248,
netPerformancePercent: 26.3184449124, netPerformancePercent: 26.3184449124,
netPerformancePercentWithCurrencyEffect: -0.04760906442310894, netPerformancePercentWithCurrencyEffect: -0.04760906442310894,
netPerformanceWithCurrencyEffect: -2732.737808972287, netPerformanceWithCurrencyEffect: -2732.737808972287,
quantity: 0.5614682, quantity: 0.5614682,
sectors: [],
symbol: 'bitcoin',
tags: [], tags: [],
url: undefined,
valueInBaseCurrency: 54666.7898248 valueInBaseCurrency: 54666.7898248
}, },
{ {
activitiesCount: 1, activitiesCount: 1,
allocationInPercentage: 0.04307127421937313, allocationInPercentage: 0.04307127421937313,
assetClass: 'EQUITY',
assetClassLabel: 'Equity',
assetProfile: { assetProfile: {
assetClass: 'EQUITY', assetClass: 'EQUITY',
assetClassLabel: 'Equity',
assetSubClass: 'STOCK', assetSubClass: 'STOCK',
assetSubClassLabel: 'Stock',
countries: [ countries: [
{ {
code: 'US', code: 'US',
@ -256,60 +188,40 @@ export const holdings: PortfolioPosition[] = [
currency: 'USD', currency: 'USD',
dataSource: 'YAHOO', dataSource: 'YAHOO',
holdings: [], holdings: [],
name: 'Microsoft Corporation',
sectors: [ sectors: [
{ {
name: 'Technology', name: 'Technology',
weight: 1 weight: 1
} }
], ],
symbol: 'MSFT' symbol: 'MSFT',
url: 'https://www.microsoft.com'
}, },
assetSubClass: 'STOCK',
assetSubClassLabel: 'Stock',
countries: [
{
code: 'US',
continent: 'North America',
name: 'United States',
weight: 1
}
],
currency: 'USD',
dataSource: 'YAHOO',
dateOfFirstActivity: new Date('2023-01-03T00:00:00.000Z'), dateOfFirstActivity: new Date('2023-01-03T00:00:00.000Z'),
dividend: 0, dividend: 0,
grossPerformance: 5065.5, grossPerformance: 5065.5,
grossPerformancePercent: 0.7047750229568411, grossPerformancePercent: 0.7047750229568411,
grossPerformancePercentWithCurrencyEffect: 0.7047750229568411, grossPerformancePercentWithCurrencyEffect: 0.7047750229568411,
grossPerformanceWithCurrencyEffect: 5065.5, grossPerformanceWithCurrencyEffect: 5065.5,
holdings: [],
investment: 7187.4, investment: 7187.4,
marketPrice: 408.43, marketPrice: 408.43,
name: 'Microsoft Corporation',
netPerformance: 5065.5, netPerformance: 5065.5,
netPerformancePercent: 0.7047750229568411, netPerformancePercent: 0.7047750229568411,
netPerformancePercentWithCurrencyEffect: -0.015973588391056275, netPerformancePercentWithCurrencyEffect: -0.015973588391056275,
netPerformanceWithCurrencyEffect: -198.899926757814, netPerformanceWithCurrencyEffect: -198.899926757814,
quantity: 30, quantity: 30,
sectors: [
{
name: 'Technology',
weight: 1
}
],
symbol: 'MSFT',
tags: [], tags: [],
url: 'https://www.microsoft.com',
valueInBaseCurrency: 12252.9 valueInBaseCurrency: 12252.9
}, },
{ {
activitiesCount: 1, activitiesCount: 1,
allocationInPercentage: 0.18762679306394897, allocationInPercentage: 0.18762679306394897,
assetClass: 'EQUITY',
assetClassLabel: 'Equity',
assetProfile: { assetProfile: {
assetClass: 'EQUITY', assetClass: 'EQUITY',
assetClassLabel: 'Equity',
assetSubClass: 'STOCK', assetSubClass: 'STOCK',
assetSubClassLabel: 'Stock',
countries: [ countries: [
{ {
code: 'US', code: 'US',
@ -321,60 +233,40 @@ export const holdings: PortfolioPosition[] = [
currency: 'USD', currency: 'USD',
dataSource: 'YAHOO', dataSource: 'YAHOO',
holdings: [], holdings: [],
name: 'Tesla, Inc.',
sectors: [ sectors: [
{ {
name: 'Consumer Discretionary', name: 'Consumer Discretionary',
weight: 1 weight: 1
} }
], ],
symbol: 'TSLA' symbol: 'TSLA',
url: 'https://www.tesla.com'
}, },
assetSubClass: 'STOCK',
assetSubClassLabel: 'Stock',
countries: [
{
code: 'US',
continent: 'North America',
name: 'United States',
weight: 1
}
],
currency: 'USD',
dataSource: 'YAHOO',
dateOfFirstActivity: new Date('2017-01-03T00:00:00.000Z'), dateOfFirstActivity: new Date('2017-01-03T00:00:00.000Z'),
dividend: 0, dividend: 0,
grossPerformance: 51227.500000005, grossPerformance: 51227.500000005,
grossPerformancePercent: 23.843379101756675, grossPerformancePercent: 23.843379101756675,
grossPerformancePercentWithCurrencyEffect: 23.843379101756675, grossPerformancePercentWithCurrencyEffect: 23.843379101756675,
grossPerformanceWithCurrencyEffect: 51227.500000005, grossPerformanceWithCurrencyEffect: 51227.500000005,
holdings: [],
investment: 2148.499999995, investment: 2148.499999995,
marketPrice: 355.84, marketPrice: 355.84,
name: 'Tesla, Inc.',
netPerformance: 51197.500000005, netPerformance: 51197.500000005,
netPerformancePercent: 23.829415871596066, netPerformancePercent: 23.829415871596066,
netPerformancePercentWithCurrencyEffect: -0.12051410125545206, netPerformancePercentWithCurrencyEffect: -0.12051410125545206,
netPerformanceWithCurrencyEffect: -7314.00091552734, netPerformanceWithCurrencyEffect: -7314.00091552734,
quantity: 150, quantity: 150,
sectors: [
{
name: 'Consumer Discretionary',
weight: 1
}
],
symbol: 'TSLA',
tags: [], tags: [],
url: 'https://www.tesla.com',
valueInBaseCurrency: 53376 valueInBaseCurrency: 53376
}, },
{ {
activitiesCount: 5, activitiesCount: 5,
allocationInPercentage: 0.053051250766657634, allocationInPercentage: 0.053051250766657634,
assetClass: 'EQUITY',
assetClassLabel: 'Equity',
assetProfile: { assetProfile: {
assetClass: 'EQUITY', assetClass: 'EQUITY',
assetClassLabel: 'Equity',
assetSubClass: 'ETF', assetSubClass: 'ETF',
assetSubClassLabel: 'ETF',
countries: [ countries: [
{ {
code: 'US', code: 'US',
@ -386,50 +278,30 @@ export const holdings: PortfolioPosition[] = [
currency: 'USD', currency: 'USD',
dataSource: 'YAHOO', dataSource: 'YAHOO',
holdings: [], holdings: [],
name: 'Vanguard Total Stock Market Index Fund ETF Shares',
sectors: [ sectors: [
{ {
name: 'Equity', name: 'Equity',
weight: 1 weight: 1
} }
], ],
symbol: 'VTI' symbol: 'VTI',
url: 'https://www.vanguard.com'
}, },
assetSubClass: 'ETF',
assetSubClassLabel: 'ETF',
countries: [
{
code: 'US',
weight: 1,
continent: 'North America',
name: 'United States'
}
],
currency: 'USD',
dataSource: 'YAHOO',
dateOfFirstActivity: new Date('2019-03-01T00:00:00.000Z'), dateOfFirstActivity: new Date('2019-03-01T00:00:00.000Z'),
dividend: 0, dividend: 0,
grossPerformance: 6845.8, grossPerformance: 6845.8,
grossPerformancePercent: 1.0164758094605268, grossPerformancePercent: 1.0164758094605268,
grossPerformancePercentWithCurrencyEffect: 1.0164758094605268, grossPerformancePercentWithCurrencyEffect: 1.0164758094605268,
grossPerformanceWithCurrencyEffect: 6845.8, grossPerformanceWithCurrencyEffect: 6845.8,
holdings: [],
investment: 8246.2, investment: 8246.2,
marketPrice: 301.84, marketPrice: 301.84,
name: 'Vanguard Total Stock Market Index Fund ETF Shares',
netPerformance: 6746.3, netPerformance: 6746.3,
netPerformancePercent: 1.0017018833976383, netPerformancePercent: 1.0017018833976383,
netPerformancePercentWithCurrencyEffect: 0.01085061564051406, netPerformancePercentWithCurrencyEffect: 0.01085061564051406,
netPerformanceWithCurrencyEffect: 161.99969482422, netPerformanceWithCurrencyEffect: 161.99969482422,
quantity: 50, quantity: 50,
sectors: [
{
name: 'Equity',
weight: 1
}
],
symbol: 'VTI',
tags: [], tags: [],
url: 'https://www.vanguard.com',
valueInBaseCurrency: 15092 valueInBaseCurrency: 15092
} }
]; ];

9
libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html

@ -29,18 +29,19 @@
[compareWith]="holdingComparisonFunction" [compareWith]="holdingComparisonFunction"
> >
<mat-select-trigger>{{ <mat-select-trigger>{{
filterForm.get('holding')?.value?.name filterForm.get('holding')?.value?.assetProfile?.name
}}</mat-select-trigger> }}</mat-select-trigger>
<mat-option [value]="null" /> <mat-option [value]="null" />
@for (holding of holdings(); track holding.name) { @for (holding of holdings(); track holding.assetProfile.name) {
<mat-option [value]="holding"> <mat-option [value]="holding">
<div class="line-height-1 text-truncate"> <div class="line-height-1 text-truncate">
<span <span
><b>{{ holding.name }}</b></span ><b>{{ holding.assetProfile.name }}</b></span
> >
<br /> <br />
<small class="text-muted" <small class="text-muted"
>{{ holding.symbol | gfSymbol }} · {{ holding.currency }}</small >{{ holding.assetProfile.symbol | gfSymbol }} ·
{{ holding.assetProfile.currency }}</small
> >
</div> </div>
</mat-option> </mat-option>

3
libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts

@ -109,7 +109,8 @@ export class GfPortfolioFilterFormComponent
} }
return ( return (
getAssetProfileIdentifier(option) === getAssetProfileIdentifier(value) getAssetProfileIdentifier(option.assetProfile) ===
getAssetProfileIdentifier(value.assetProfile)
); );
} }

Loading…
Cancel
Save