Browse Source

Merge 4f3c950e2b into 0638da9490

pull/6499/merge
Thomas Kaul 2 weeks ago
committed by GitHub
parent
commit
b51213f0be
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 23
      apps/api/src/app/endpoints/ai/ai.service.ts
  3. 12
      apps/api/src/app/endpoints/public/public.controller.ts
  4. 29
      apps/api/src/app/portfolio/portfolio.controller.ts
  5. 34
      apps/api/src/app/portfolio/portfolio.service.ts
  6. 10
      apps/api/src/models/rule.ts
  7. 3
      apps/api/src/models/rules/asset-class-cluster-risk/equity.ts
  8. 3
      apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts
  9. 2
      apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts
  10. 2
      apps/api/src/models/rules/currency-cluster-risk/current-investment.ts
  11. 33
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  12. 5
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
  13. 12
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html
  14. 65
      apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
  15. 16
      apps/client/src/app/pages/portfolio/analysis/analysis-page.html
  16. 26
      apps/client/src/app/pages/public/public-page.component.ts
  17. 14
      libs/common/src/lib/helper.ts
  18. 45
      libs/common/src/lib/interfaces/portfolio-position.interface.ts
  19. 22
      libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts
  20. 38
      libs/ui/src/lib/assistant/assistant.component.ts
  21. 18
      libs/ui/src/lib/holdings-table/holdings-table.component.html
  22. 4
      libs/ui/src/lib/holdings-table/holdings-table.component.ts
  23. 12
      libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html
  24. 5
      libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts
  25. 2
      libs/ui/src/lib/top-holdings/top-holdings.component.html
  26. 4
      libs/ui/src/lib/top-holdings/top-holdings.component.ts
  27. 14
      libs/ui/src/lib/treemap-chart/treemap-chart.component.ts

2
CHANGELOG.md

@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Switched to using asset profile data from the endpoint `GET api/v1/portfolio/holdings`
- Switched to using asset profile data from the holdings of the public page
- Consolidated the sign-out logic within the user service to unify cookie, state and token clearance - Consolidated the sign-out logic within the user service to unify cookie, state and token clearance
- Improved the language localization for Polish (`pl`) - Improved the language localization for Polish (`pl`)
- Upgraded `@ionic/angular` from version `8.7.3` to `8.8.1` - Upgraded `@ionic/angular` from version `8.7.3` to `8.8.1`

23
apps/api/src/app/endpoints/ai/ai.service.ts

@ -89,15 +89,7 @@ export class AiService {
.sort((a, b) => { .sort((a, b) => {
return b.allocationInPercentage - a.allocationInPercentage; return b.allocationInPercentage - a.allocationInPercentage;
}) })
.map( .map(({ allocationInPercentage, assetProfile }) => {
({
allocationInPercentage,
assetClass,
assetSubClass,
currency,
name: label,
symbol
}) => {
return AiService.HOLDINGS_TABLE_COLUMN_DEFINITIONS.reduce( return AiService.HOLDINGS_TABLE_COLUMN_DEFINITIONS.reduce(
(row, { key, name }) => { (row, { key, name }) => {
switch (key) { switch (key) {
@ -106,23 +98,23 @@ export class AiService {
break; break;
case 'ASSET_CLASS': case 'ASSET_CLASS':
row[name] = assetClass ?? ''; row[name] = assetProfile?.assetClass ?? '';
break; break;
case 'ASSET_SUB_CLASS': case 'ASSET_SUB_CLASS':
row[name] = assetSubClass ?? ''; row[name] = assetProfile?.assetSubClass ?? '';
break; break;
case 'CURRENCY': case 'CURRENCY':
row[name] = currency; row[name] = assetProfile?.currency ?? '';
break; break;
case 'NAME': case 'NAME':
row[name] = label; row[name] = assetProfile?.name ?? '';
break; break;
case 'SYMBOL': case 'SYMBOL':
row[name] = symbol; row[name] = assetProfile?.symbol ?? '';
break; break;
default: default:
@ -134,8 +126,7 @@ export class AiService {
}, },
{} as Record<string, string> {} as Record<string, string>
); );
} });
);
// Dynamic import to load ESM module from CommonJS context // Dynamic import to load ESM module from CommonJS context
// eslint-disable-next-line @typescript-eslint/no-implied-eval // eslint-disable-next-line @typescript-eslint/no-implied-eval

12
apps/api/src/app/endpoints/public/public.controller.ts

@ -150,11 +150,11 @@ export class PublicController {
}; };
const totalValue = getSum( const totalValue = getSum(
Object.values(holdings).map(({ currency, marketPrice, quantity }) => { Object.values(holdings).map(({ assetProfile, marketPrice, quantity }) => {
return new Big( return new Big(
this.exchangeRateDataService.toCurrency( this.exchangeRateDataService.toCurrency(
quantity * marketPrice, quantity * marketPrice,
currency, assetProfile.currency,
this.request.user?.settings?.settings.baseCurrency ?? this.request.user?.settings?.settings.baseCurrency ??
DEFAULT_CURRENCY DEFAULT_CURRENCY
) )
@ -166,19 +166,11 @@ export class PublicController {
publicPortfolioResponse.holdings[symbol] = { publicPortfolioResponse.holdings[symbol] = {
allocationInPercentage: allocationInPercentage:
portfolioPosition.valueInBaseCurrency / totalValue, portfolioPosition.valueInBaseCurrency / totalValue,
assetClass: hasDetails ? portfolioPosition.assetClass : undefined,
assetProfile: hasDetails ? portfolioPosition.assetProfile : undefined, assetProfile: hasDetails ? portfolioPosition.assetProfile : undefined,
countries: hasDetails ? portfolioPosition.countries : [],
currency: hasDetails ? portfolioPosition.currency : undefined,
dataSource: portfolioPosition.dataSource,
dateOfFirstActivity: portfolioPosition.dateOfFirstActivity, dateOfFirstActivity: portfolioPosition.dateOfFirstActivity,
markets: hasDetails ? portfolioPosition.markets : undefined, markets: hasDetails ? portfolioPosition.markets : undefined,
name: portfolioPosition.name,
netPerformancePercentWithCurrencyEffect: netPerformancePercentWithCurrencyEffect:
portfolioPosition.netPerformancePercentWithCurrencyEffect, portfolioPosition.netPerformancePercentWithCurrencyEffect,
sectors: hasDetails ? portfolioPosition.sectors : [],
symbol: portfolioPosition.symbol,
url: portfolioPosition.url,
valueInPercentage: portfolioPosition.valueInBaseCurrency / totalValue valueInPercentage: portfolioPosition.valueInBaseCurrency / totalValue
}; };
} }

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

@ -141,10 +141,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 }) => {
@ -212,24 +212,29 @@ export class PortfolioController {
} }
for (const [symbol, portfolioPosition] of Object.entries(holdings)) { for (const [symbol, portfolioPosition] of Object.entries(holdings)) {
const assetProfile = portfolioPosition.assetProfile;
holdings[symbol] = { holdings[symbol] = {
...portfolioPosition, ...portfolioPosition,
assetProfile: {
...assetProfile,
assetClass: assetClass:
hasDetails || portfolioPosition.assetClass === AssetClass.LIQUIDITY hasDetails || assetProfile.assetClass === AssetClass.LIQUIDITY
? portfolioPosition.assetClass ? assetProfile.assetClass
: undefined, : undefined,
assetSubClass: assetSubClass:
hasDetails || portfolioPosition.assetSubClass === AssetSubClass.CASH hasDetails || assetProfile.assetSubClass === AssetSubClass.CASH
? portfolioPosition.assetSubClass ? assetProfile.assetSubClass
: undefined, : undefined,
countries: hasDetails ? portfolioPosition.countries : [], countries: hasDetails ? assetProfile.countries : [],
currency: hasDetails ? portfolioPosition.currency : undefined, currency: hasDetails ? assetProfile.currency : undefined,
holdings: hasDetails ? portfolioPosition.holdings : [], holdings: hasDetails ? assetProfile.holdings : [],
sectors: hasDetails ? assetProfile.sectors : []
},
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

@ -570,7 +570,6 @@ export class PortfolioService {
for (const { for (const {
activitiesCount, activitiesCount,
currency,
dateOfFirstActivity, dateOfFirstActivity,
dividend, dividend,
grossPerformance, grossPerformance,
@ -613,16 +612,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,
@ -645,9 +641,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,
@ -656,19 +649,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:
@ -678,8 +659,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()
}; };
} }
@ -1447,8 +1426,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 +=
@ -1694,11 +1673,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,
@ -1710,25 +1686,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
}; };

10
apps/api/src/models/rule.ts

@ -1,6 +1,5 @@
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config'; import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config';
import { groupBy } from '@ghostfolio/common/helper';
import { import {
PortfolioPosition, PortfolioPosition,
PortfolioReportRule, PortfolioReportRule,
@ -9,6 +8,7 @@ import {
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { Big } from 'big.js'; import { Big } from 'big.js';
import { groupBy } from 'lodash';
import { EvaluationResult } from './interfaces/evaluation-result.interface'; import { EvaluationResult } from './interfaces/evaluation-result.interface';
import { RuleInterface } from './interfaces/rule.interface'; import { RuleInterface } from './interfaces/rule.interface';
@ -41,10 +41,12 @@ export abstract class Rule<T extends RuleSettings> implements RuleInterface<T> {
public groupCurrentHoldingsByAttribute( public groupCurrentHoldingsByAttribute(
holdings: PortfolioPosition[], holdings: PortfolioPosition[],
attribute: keyof PortfolioPosition, attribute:
| keyof PortfolioPosition
| `assetProfile.${Extract<keyof PortfolioPosition['assetProfile'], string>}`,
baseCurrency: string baseCurrency: string
) { ) {
return Array.from(groupBy(attribute, holdings).entries()).map( return Object.entries(groupBy(holdings, attribute)).map(
([attributeValue, objs]) => ({ ([attributeValue, objs]) => ({
groupKey: attributeValue, groupKey: attributeValue,
investment: objs.reduce( investment: objs.reduce(
@ -59,7 +61,7 @@ export abstract class Rule<T extends RuleSettings> implements RuleInterface<T> {
new Big(currentValue.quantity) new Big(currentValue.quantity)
.mul(currentValue.marketPrice ?? 0) .mul(currentValue.marketPrice ?? 0)
.toNumber(), .toNumber(),
currentValue.currency, currentValue.assetProfile.currency,
baseCurrency baseCurrency
), ),
0 0

3
apps/api/src/models/rules/asset-class-cluster-risk/equity.ts

@ -27,9 +27,10 @@ export class AssetClassClusterRiskEquity extends Rule<Settings> {
public evaluate(ruleSettings: Settings) { public evaluate(ruleSettings: Settings) {
const holdingsGroupedByAssetClass = this.groupCurrentHoldingsByAttribute( const holdingsGroupedByAssetClass = this.groupCurrentHoldingsByAttribute(
this.holdings, this.holdings,
'assetClass', 'assetProfile.assetClass',
ruleSettings.baseCurrency ruleSettings.baseCurrency
); );
let totalValue = 0; let totalValue = 0;
const equityValueInBaseCurrency = const equityValueInBaseCurrency =

3
apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts

@ -27,9 +27,10 @@ export class AssetClassClusterRiskFixedIncome extends Rule<Settings> {
public evaluate(ruleSettings: Settings) { public evaluate(ruleSettings: Settings) {
const holdingsGroupedByAssetClass = this.groupCurrentHoldingsByAttribute( const holdingsGroupedByAssetClass = this.groupCurrentHoldingsByAttribute(
this.holdings, this.holdings,
'assetClass', 'assetProfile.assetClass',
ruleSettings.baseCurrency ruleSettings.baseCurrency
); );
let totalValue = 0; let totalValue = 0;
const fixedIncomeValueInBaseCurrency = const fixedIncomeValueInBaseCurrency =

2
apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts

@ -27,7 +27,7 @@ export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Setti
public evaluate(ruleSettings: Settings) { public evaluate(ruleSettings: Settings) {
const holdingsGroupedByCurrency = this.groupCurrentHoldingsByAttribute( const holdingsGroupedByCurrency = this.groupCurrentHoldingsByAttribute(
this.holdings, this.holdings,
'currency', 'assetProfile.currency',
ruleSettings.baseCurrency ruleSettings.baseCurrency
); );

2
apps/api/src/models/rules/currency-cluster-risk/current-investment.ts

@ -27,7 +27,7 @@ export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> {
public evaluate(ruleSettings: Settings) { public evaluate(ruleSettings: Settings) {
const holdingsGroupedByCurrency = this.groupCurrentHoldingsByAttribute( const holdingsGroupedByCurrency = this.groupCurrentHoldingsByAttribute(
this.holdings, this.holdings,
'currency', 'assetProfile.currency',
ruleSettings.baseCurrency ruleSettings.baseCurrency
); );

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

@ -136,34 +136,25 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ holdings }) => { .subscribe(({ holdings }) => {
this.defaultLookupItems = holdings this.defaultLookupItems = holdings
.filter(({ assetSubClass }) => { .filter(({ assetProfile }) => {
return !['CASH'].includes(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( .map(({ assetProfile }) => {
({
assetClass,
assetSubClass,
currency,
dataSource,
name,
symbol
}) => {
return { return {
assetClass, assetClass: assetProfile.assetClass,
assetSubClass, assetSubClass: assetProfile.assetSubClass,
currency, currency: assetProfile.currency,
dataSource,
name,
symbol,
dataProviderInfo: { dataProviderInfo: {
isPremium: false isPremium: false
} },
dataSource: assetProfile.dataSource,
name: assetProfile.name,
symbol: assetProfile.symbol
}; };
} });
);
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });

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

@ -154,9 +154,10 @@ export class GfImportActivitiesDialogComponent implements OnDestroy {
}) })
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ holdings }) => { .subscribe(({ holdings }) => {
this.holdings = sortBy(holdings, ({ name }) => { this.holdings = sortBy(holdings, ({ assetProfile }) => {
return name.toLowerCase(); return assetProfile.name.toLowerCase();
}); });
this.assetProfileForm.get('assetProfileIdentifier').enable(); this.assetProfileForm.get('assetProfileIdentifier').enable();
this.isLoading = false; this.isLoading = false;

12
apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html

@ -38,18 +38,18 @@
<mat-option <mat-option
class="line-height-1" class="line-height-1"
[value]="{ [value]="{
dataSource: holding.dataSource, dataSource: holding.assetProfile.dataSource,
name: holding.name, name: holding.assetProfile.name,
symbol: holding.symbol symbol: holding.assetProfile.symbol
}" }"
> >
<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.assetProfile.symbol | gfSymbol }} ·
{{ holding.currency }}</small {{ holding.assetProfile.currency }}</small
> >
</mat-option> </mat-option>
} }

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

@ -74,16 +74,10 @@ export class GfAllocationsPageComponent implements OnInit {
public deviceType: string; public deviceType: string;
public hasImpersonationId: boolean; public hasImpersonationId: boolean;
public holdings: { public holdings: {
[symbol: string]: Pick< [symbol: string]: Pick<PortfolioPosition, 'assetProfile' | 'exchange'> & {
PortfolioPosition, etfProvider: string;
| 'assetClass' value: number;
| 'assetClassLabel' };
| 'assetSubClass'
| 'assetSubClassLabel'
| 'currency'
| 'exchange'
| 'name'
> & { etfProvider: string; value: number };
}; };
public isLoading = false; public isLoading = false;
public markets: { public markets: {
@ -209,7 +203,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') {
@ -326,6 +320,7 @@ export class GfAllocationsPageComponent implements OnInit {
for (const [symbol, position] of Object.entries( for (const [symbol, position] of Object.entries(
this.portfolioDetails.holdings this.portfolioDetails.holdings
)) { )) {
const { assetProfile } = position;
let value = 0; let value = 0;
if (this.hasImpersonationId) { if (this.hasImpersonationId) {
@ -336,24 +331,28 @@ export class GfAllocationsPageComponent implements OnInit {
this.holdings[symbol] = { this.holdings[symbol] = {
value, value,
assetClass: position.assetClass || (UNKNOWN_KEY as AssetClass), assetProfile: {
assetClassLabel: position.assetClassLabel || UNKNOWN_KEY, ...assetProfile,
assetSubClass: position.assetSubClass || (UNKNOWN_KEY as AssetSubClass), assetClass: assetProfile.assetClass || (UNKNOWN_KEY as AssetClass),
assetSubClassLabel: position.assetSubClassLabel || UNKNOWN_KEY, assetClassLabel: assetProfile.assetClassLabel || UNKNOWN_KEY,
currency: position.currency, assetSubClass:
assetProfile.assetSubClass || (UNKNOWN_KEY as AssetSubClass),
assetSubClassLabel: assetProfile.assetSubClassLabel || UNKNOWN_KEY,
currency: assetProfile.currency,
name: assetProfile.name
},
etfProvider: this.extractEtfProvider({ etfProvider: this.extractEtfProvider({
assetSubClass: position.assetSubClass, assetSubClass: assetProfile.assetSubClass,
name: position.name name: assetProfile.name
}), }),
exchange: position.exchange, exchange: position.exchange
name: position.name
}; };
if (position.assetClass !== AssetClass.LIQUIDITY) { if (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 (assetProfile.countries.length > 0) {
for (const country of position.countries) { for (const country of assetProfile.countries) {
const { code, continent, name, weight } = country; const { code, continent, name, weight } = country;
if (this.continents[continent]?.value) { if (this.continents[continent]?.value) {
@ -404,8 +403,8 @@ export class GfAllocationsPageComponent implements OnInit {
: this.portfolioDetails.holdings[symbol].valueInPercentage; : this.portfolioDetails.holdings[symbol].valueInPercentage;
} }
if (position.holdings.length > 0) { if (assetProfile.holdings.length > 0) {
for (const holding of position.holdings) { for (const holding of assetProfile.holdings) {
const { allocationInPercentage, name, valueInBaseCurrency } = const { allocationInPercentage, name, valueInBaseCurrency } =
holding; holding;
@ -426,8 +425,8 @@ export class GfAllocationsPageComponent implements OnInit {
} }
} }
if (position.sectors.length > 0) { if (assetProfile.sectors.length > 0) {
for (const sector of position.sectors) { for (const sector of assetProfile.sectors) {
const { name, weight } = sector; const { name, weight } = sector;
if (this.sectors[name]?.value) { if (this.sectors[name]?.value) {
@ -456,13 +455,13 @@ export class GfAllocationsPageComponent implements OnInit {
} }
} }
if (this.holdings[symbol].assetSubClass === 'ETF') { if (this.holdings[symbol].assetProfile.assetSubClass === 'ETF') {
this.totalValueInEtf += this.holdings[symbol].value; this.totalValueInEtf += this.holdings[symbol].value;
} }
this.symbols[prettifySymbol(symbol)] = { this.symbols[prettifySymbol(symbol)] = {
dataSource: position.dataSource, dataSource: assetProfile.dataSource,
name: position.name, name: assetProfile.name,
symbol: prettifySymbol(symbol), symbol: prettifySymbol(symbol),
value: isNumber(position.valueInBaseCurrency) value: isNumber(position.valueInBaseCurrency)
? position.valueInBaseCurrency ? position.valueInBaseCurrency
@ -515,8 +514,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 parentHolding.name === name; return parentHolding.name === name;
} }
@ -526,7 +525,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

@ -312,13 +312,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"
@ -361,13 +363,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"

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

@ -71,7 +71,11 @@ export class GfPublicPageComponent implements OnInit {
}; };
public pageSize = Number.MAX_SAFE_INTEGER; public pageSize = Number.MAX_SAFE_INTEGER;
public positions: { public positions: {
[symbol: string]: Pick<PortfolioPosition, 'currency' | 'name'> & { [symbol: string]: {
assetProfile: Pick<
PortfolioPosition['assetProfile'],
'currency' | 'name'
>;
value: number; value: number;
}; };
}; };
@ -168,19 +172,23 @@ export class GfPublicPageComponent implements OnInit {
for (const [symbol, position] of Object.entries( for (const [symbol, position] of Object.entries(
this.publicPortfolioDetails.holdings this.publicPortfolioDetails.holdings
)) { )) {
const { assetProfile } = position;
this.holdings.push(position); this.holdings.push(position);
this.positions[symbol] = { this.positions[symbol] = {
currency: position.currency, assetProfile: {
name: position.name, currency: assetProfile.currency,
name: assetProfile.name
},
value: position.allocationInPercentage value: position.allocationInPercentage
}; };
if (position.assetClass !== AssetClass.LIQUIDITY) { if (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 (assetProfile.countries.length > 0) {
for (const country of position.countries) { for (const country of assetProfile.countries) {
const { code, continent, name, weight } = country; const { code, continent, name, weight } = country;
if (this.continents[continent]?.value) { if (this.continents[continent]?.value) {
@ -217,8 +225,8 @@ export class GfPublicPageComponent implements OnInit {
this.publicPortfolioDetails.holdings[symbol].valueInBaseCurrency; this.publicPortfolioDetails.holdings[symbol].valueInBaseCurrency;
} }
if (position.sectors.length > 0) { if (assetProfile.sectors.length > 0) {
for (const sector of position.sectors) { for (const sector of assetProfile.sectors) {
const { name, weight } = sector; const { name, weight } = sector;
if (this.sectors[name]?.value) { if (this.sectors[name]?.value) {
@ -240,7 +248,7 @@ export class GfPublicPageComponent implements OnInit {
} }
this.symbols[prettifySymbol(symbol)] = { this.symbols[prettifySymbol(symbol)] = {
name: position.name, name: assetProfile.name,
symbol: prettifySymbol(symbol), symbol: prettifySymbol(symbol),
value: isNumber(position.valueInBaseCurrency) value: isNumber(position.valueInBaseCurrency)
? position.valueInBaseCurrency ? position.valueInBaseCurrency

14
libs/common/src/lib/helper.ts

@ -342,20 +342,6 @@ export function getYesterday() {
return subDays(new Date(Date.UTC(year, month, day)), 1); return subDays(new Date(Date.UTC(year, month, day)), 1);
} }
export function groupBy<T, K extends keyof T>(
key: K,
arr: T[]
): Map<T[K], T[]> {
const map = new Map<T[K], T[]>();
arr.forEach((t) => {
if (!map.has(t[key])) {
map.set(t[key], []);
}
map.get(t[key])!.push(t);
});
return map;
}
export function interpolate(template: string, context: any) { export function interpolate(template: string, context: any) {
return template?.replace(/[$]{([^}]+)}/g, (_, objectPath) => { return template?.replace(/[$]{([^}]+)}/g, (_, objectPath) => {
const properties = objectPath.split('.'); const properties = objectPath.split('.');

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

@ -1,22 +1,13 @@
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'
@ -34,21 +25,6 @@ export interface PortfolioPosition {
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 +32,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;
} }

22
libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts

@ -14,32 +14,10 @@ export interface PublicPortfolioResponse extends PublicPortfolioResponseV1 {
[symbol: string]: Pick< [symbol: string]: Pick<
PortfolioPosition, PortfolioPosition,
| 'allocationInPercentage' | 'allocationInPercentage'
/** @deprecated */
| 'assetClass'
| 'assetProfile' | 'assetProfile'
/** @deprecated */
| 'countries'
| 'currency'
/** @deprecated */
| 'dataSource'
| 'dateOfFirstActivity' | 'dateOfFirstActivity'
| 'markets' | 'markets'
/** @deprecated */
| 'name'
| 'netPerformancePercentWithCurrencyEffect' | 'netPerformancePercentWithCurrencyEffect'
/** @deprecated */
| 'sectors'
/** @deprecated */
| 'symbol'
/** @deprecated */
| 'url'
| 'valueInBaseCurrency' | 'valueInBaseCurrency'
| 'valueInPercentage' | 'valueInPercentage'
>; >;

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

@ -480,11 +480,14 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.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();
@ -506,11 +509,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'
}, },
{ {
@ -697,18 +700,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
}; };
} });
);
}), }),
takeUntil(this.unsubscribeSubject) takeUntil(this.unsubscribeSubject)
); );
@ -752,12 +753,13 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
'filters.dataSource' 'filters.dataSource'
] as DataSource; ] as DataSource;
const symbol = this.user?.settings?.['filters.symbol']; const symbol = this.user?.settings?.['filters.symbol'];
const selectedHolding = this.holdings.find((holding) => {
const selectedHolding = this.holdings.find(({ assetProfile }) => {
return ( return (
!!(dataSource && symbol) && !!(dataSource && symbol) &&
getAssetProfileIdentifier({ getAssetProfileIdentifier({
dataSource: holding.dataSource, dataSource: assetProfile.dataSource,
symbol: holding.symbol symbol: assetProfile.symbol
}) === getAssetProfileIdentifier({ dataSource, symbol }) }) === getAssetProfileIdentifier({ dataSource, symbol })
); );
}); });

18
libs/ui/src/lib/holdings-table/holdings-table.component.html

@ -11,9 +11,9 @@
<th *matHeaderCellDef class="px-1" mat-header-cell></th> <th *matHeaderCellDef class="px-1" mat-header-cell></th>
<td *matCellDef="let element" class="px-1 text-center" mat-cell> <td *matCellDef="let element" class="px-1 text-center" mat-cell>
<gf-entity-logo <gf-entity-logo
[dataSource]="element.dataSource" [dataSource]="element.assetProfile.dataSource"
[symbol]="element.symbol" [symbol]="element.assetProfile.symbol"
[tooltip]="element.name" [tooltip]="element.assetProfile.name"
/> />
</td> </td>
</ng-container> </ng-container>
@ -24,13 +24,13 @@
</th> </th>
<td *matCellDef="let element" class="line-height-1 px-1" mat-cell> <td *matCellDef="let element" class="line-height-1 px-1" mat-cell>
<div class="text-truncate"> <div class="text-truncate">
{{ element.name }} {{ element.assetProfile.name }}
@if (element.name === element.symbol) { @if (element.assetProfile.name === element.assetProfile.symbol) {
<span>({{ element.assetSubClassLabel }})</span> <span>({{ element.assetProfile.assetSubClassLabel }})</span>
} }
</div> </div>
<div> <div>
<small class="text-muted">{{ element.symbol }}</small> <small class="text-muted">{{ element.assetProfile.symbol }}</small>
</div> </div>
</td> </td>
</ng-container> </ng-container>
@ -185,8 +185,8 @@
(click)=" (click)="
canShowDetails(row) && canShowDetails(row) &&
onOpenHoldingDialog({ onOpenHoldingDialog({
dataSource: row.dataSource, dataSource: row.assetProfile.dataSource,
symbol: row.symbol symbol: row.assetProfile.symbol
}) })
" "
></tr> ></tr>

4
libs/ui/src/lib/holdings-table/holdings-table.component.ts

@ -102,10 +102,10 @@ export class GfHoldingsTableComponent {
}); });
} }
protected canShowDetails(holding: PortfolioPosition): boolean { protected canShowDetails({ assetProfile }: PortfolioPosition): boolean {
return ( return (
this.hasPermissionToOpenDetails() && this.hasPermissionToOpenDetails() &&
!this.ignoreAssetSubClasses.includes(holding.assetProfile.assetSubClass) !this.ignoreAssetSubClasses.includes(assetProfile.assetSubClass)
); );
} }

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

@ -29,18 +29,22 @@
[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 getAssetProfileIdentifier(holding.assetProfile)
) {
<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.currency }}</small
> >
</div> </div>
</mat-option> </mat-option>

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

@ -63,6 +63,8 @@ export class GfPortfolioFilterFormComponent
public readonly holdings = input<PortfolioPosition[]>([]); public readonly holdings = input<PortfolioPosition[]>([]);
public readonly tags = input<Filter[]>([]); public readonly tags = input<Filter[]>([]);
public getAssetProfileIdentifier = getAssetProfileIdentifier;
public filterForm: FormGroup<{ public filterForm: FormGroup<{
account: FormControl<string | null>; account: FormControl<string | null>;
assetClass: FormControl<string | null>; assetClass: FormControl<string | null>;
@ -109,7 +111,8 @@ export class GfPortfolioFilterFormComponent
} }
return ( return (
getAssetProfileIdentifier(option) === getAssetProfileIdentifier(value) getAssetProfileIdentifier(option.assetProfile) ===
getAssetProfileIdentifier(value.assetProfile)
); );
} }

2
libs/ui/src/lib/top-holdings/top-holdings.component.html

@ -121,7 +121,7 @@
*matRowDef="let row; columns: displayedColumns" *matRowDef="let row; columns: displayedColumns"
mat-row mat-row
[ngClass]="{ 'cursor-pointer': row.position }" [ngClass]="{ 'cursor-pointer': row.position }"
(click)="onClickHolding(row.position)" (click)="onClickHolding(row.position.assetProfile)"
></tr> ></tr>
<tr <tr
*matFooterRowDef="displayedColumns" *matFooterRowDef="displayedColumns"

4
libs/ui/src/lib/top-holdings/top-holdings.component.ts

@ -89,8 +89,8 @@ export class GfTopHoldingsComponent implements OnChanges, OnDestroy {
} }
} }
public onClickHolding(assetProfileIdentifier: AssetProfileIdentifier) { public onClickHolding({ dataSource, symbol }: AssetProfileIdentifier) {
this.holdingClicked.emit(assetProfileIdentifier); this.holdingClicked.emit({ dataSource, symbol });
} }
public onShowAllHoldings() { public onShowAllHoldings() {

14
libs/ui/src/lib/treemap-chart/treemap-chart.component.ts

@ -278,8 +278,8 @@ export class GfTreemapChartComponent
); );
} }
const name = raw._data.name; const name = raw._data.assetProfile.name;
const symbol = raw._data.symbol; const symbol = raw._data.assetProfile.symbol;
return [ return [
isUUID(symbol) ? (name ?? symbol) : symbol, isUUID(symbol) ? (name ?? symbol) : symbol,
@ -320,8 +320,10 @@ export class GfTreemapChartComponent
['desc'] ['desc']
) as PortfolioPosition[]; ) as PortfolioPosition[];
const dataSource: DataSource = dataset[dataIndex].dataSource; const dataSource: DataSource =
const symbol: string = dataset[dataIndex].symbol; dataset[dataIndex].assetProfile.dataSource;
const symbol: string = dataset[dataIndex].assetProfile.symbol;
this.treemapChartClicked.emit({ dataSource, symbol }); this.treemapChartClicked.emit({ dataSource, symbol });
} catch {} } catch {}
@ -355,10 +357,10 @@ export class GfTreemapChartComponent
callbacks: { callbacks: {
label: ({ raw }: GfTreemapTooltipItem) => { label: ({ raw }: GfTreemapTooltipItem) => {
const allocationInPercentage = `${(raw._data.allocationInPercentage * 100).toFixed(2)}%`; const allocationInPercentage = `${(raw._data.allocationInPercentage * 100).toFixed(2)}%`;
const name = raw._data.name; const name = raw._data.assetProfile.name;
const sign = const sign =
raw._data.netPerformancePercentWithCurrencyEffect > 0 ? '+' : ''; raw._data.netPerformancePercentWithCurrencyEffect > 0 ? '+' : '';
const symbol = raw._data.symbol; const symbol = raw._data.assetProfile.symbol;
const netPerformanceInPercentageWithSign = `${sign}${(raw._data.netPerformancePercentWithCurrencyEffect * 100).toFixed(2)}%`; const netPerformanceInPercentageWithSign = `${sign}${(raw._data.netPerformancePercentWithCurrencyEffect * 100).toFixed(2)}%`;

Loading…
Cancel
Save