diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c28bcd0e6..488d1f1161 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Extended the tool to get the accounts of the portfolio in the server of the Model Context Protocol (MCP) to support the filtering by account (experimental) +- Upgraded `undici` from version `8.5.0` to `8.10.0` - Upgraded `uuid` from version `14.0.1` to `14.0.2` ### Fixed +- Fixed the cash positions being included in the by continent, by country and by sector charts on the allocations page and the public page +- Fixed the allocations in percentage exceeding 100% in the restricted view - Fixed the portfolio calculation for holdings with activities before the first known historical market price by falling back to the unit price of the activity ## 3.64.0 - 2026-08-30 diff --git a/apps/api/src/app/endpoints/ai/ai.service.spec.ts b/apps/api/src/app/endpoints/ai/ai.service.spec.ts index 3b64791548..6328df1952 100644 --- a/apps/api/src/app/endpoints/ai/ai.service.spec.ts +++ b/apps/api/src/app/endpoints/ai/ai.service.spec.ts @@ -29,13 +29,16 @@ interface AiServiceWithMarkdownTable { } function createAccount({ + id = 'account-a-id', isExcluded = false, name = 'Account A' }: { + id?: string; isExcluded?: boolean; name?: string; } = {}) { return { + id, name, activitiesCount: 3, allocationInPercentage: 0.25, @@ -86,6 +89,7 @@ describe('AiService', () => { describe('getAccountsTableColumnNames', () => { it('gives no column with a monetary value', () => { expect(AiService.getAccountsTableColumnNames()).toEqual([ + 'Id', 'Name', 'Currency', 'Platform', @@ -121,10 +125,20 @@ describe('AiService', () => { expect(result).not.toContain('2000'); }); + // The accountIds parameter of the tool takes the identifiers, hence the + // table has to give them + it('gives the identifier of an account', async () => { + const aiService = createAiService([createAccount()]); + + const result = await aiService.getAccountsTable({ userId: 'user-id' }); + + expect(result).toContain('account-a-id'); + }); + it('marks an account which is excluded from the analysis', async () => { const aiService = createAiService([ createAccount({ isExcluded: true }), - createAccount({ name: 'Account B' }) + createAccount({ id: 'account-b-id', name: 'Account B' }) ]); const result = await aiService.getAccountsTable({ userId: 'user-id' }); @@ -132,7 +146,7 @@ describe('AiService', () => { const [rowOfAccountA, rowOfAccountB] = result .split('\n') .filter((line) => { - return line.startsWith('Account '); + return line.startsWith('account-'); }); expect(rowOfAccountA).toContain('true'); diff --git a/apps/api/src/app/endpoints/ai/ai.service.ts b/apps/api/src/app/endpoints/ai/ai.service.ts index 029fcc81bf..758499c6e6 100644 --- a/apps/api/src/app/endpoints/ai/ai.service.ts +++ b/apps/api/src/app/endpoints/ai/ai.service.ts @@ -30,9 +30,11 @@ export class AiService { | 'ALLOCATION_PERCENTAGE' | 'CURRENCY' | 'EXCLUDED_FROM_ANALYSIS' + | 'ID' | 'NAME' | 'PLATFORM'; } & ColumnDescriptor)[] = [ + { key: 'ID', name: 'Id' }, { key: 'NAME', name: 'Name' }, { key: 'CURRENCY', name: 'Currency' }, { key: 'PLATFORM', name: 'Platform' }, @@ -160,6 +162,7 @@ export class AiService { activitiesCount, allocationInPercentage, currency, + id, name: label, platform, tags @@ -183,6 +186,10 @@ export class AiService { row[name] = isAccountExcluded({ tags }).toString(); break; + case 'ID': + row[name] = id; + break; + case 'NAME': row[name] = label ?? ''; break; diff --git a/apps/api/src/app/endpoints/mcp/mcp.controller.ts b/apps/api/src/app/endpoints/mcp/mcp.controller.ts index f238b4890e..f8261f5c05 100644 --- a/apps/api/src/app/endpoints/mcp/mcp.controller.ts +++ b/apps/api/src/app/endpoints/mcp/mcp.controller.ts @@ -8,6 +8,7 @@ import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper' import { DATE_RANGES, DEFAULT_LANGUAGE_CODE, + MCP_MAX_ACCOUNTS, MCP_MAX_ACTIVITIES } from '@ghostfolio/common/config'; import { scopes } from '@ghostfolio/common/scopes'; @@ -20,6 +21,14 @@ import { McpController, Tool } from '@rekog/mcp-nest'; import { z } from 'zod'; const GET_ACCOUNTS_PARAMETERS = z.object({ + accountIds: z + .array(z.string().min(1)) + .min(1) + .max(MCP_MAX_ACCOUNTS) + .optional() + .describe( + `The identifiers of the accounts to get, at most ${MCP_MAX_ACCOUNTS}` + ), assetClasses: z .array(z.enum(AssetClass)) .min(1) @@ -104,9 +113,14 @@ export class GhostfolioMcpController { public async getAccounts( @Impersonation() { userId }: ImpersonationContext, @Payload() - { assetClasses, holding }: z.infer + { + accountIds, + assetClasses, + holding + }: z.infer ) { const filters = this.apiService.buildFiltersFromQueryParams({ + filterByAccounts: accountIds?.join(','), filterByAssetClasses: assetClasses?.join(','), filterByDataSource: holding?.dataSource, filterBySymbol: holding?.symbol diff --git a/apps/api/src/app/endpoints/public/public.service.ts b/apps/api/src/app/endpoints/public/public.service.ts index d86f1f0d8e..c32df6d82d 100644 --- a/apps/api/src/app/endpoints/public/public.service.ts +++ b/apps/api/src/app/endpoints/public/public.service.ts @@ -6,18 +6,14 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/con import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; import { SubscriptionType } from '@ghostfolio/common/enums'; -import { getSum } from '@ghostfolio/common/helper'; +import { getSum, isCashPosition } from '@ghostfolio/common/helper'; import { AccessSettings, PublicPortfolioResponse } from '@ghostfolio/common/interfaces'; import { HttpException, Injectable } from '@nestjs/common'; -import { - AssetClass, - AssetSubClass, - Type as ActivityType -} from '@prisma/client'; +import { Type as ActivityType } from '@prisma/client'; import { Big } from 'big.js'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; @@ -173,23 +169,19 @@ export class PublicService { assetProfile: { ...holding.assetProfile, assetClass: - hasDetails || - holding.assetProfile.assetClass === AssetClass.LIQUIDITY + hasDetails || isCashPosition(holding.assetProfile) ? holding.assetProfile.assetClass : undefined, assetClassLabel: - hasDetails || - holding.assetProfile.assetClass === AssetClass.LIQUIDITY + hasDetails || isCashPosition(holding.assetProfile) ? holding.assetProfile.assetClassLabel : undefined, assetSubClass: - hasDetails || - holding.assetProfile.assetSubClass === AssetSubClass.CASH + hasDetails || isCashPosition(holding.assetProfile) ? holding.assetProfile.assetSubClass : undefined, assetSubClassLabel: - hasDetails || - holding.assetProfile.assetSubClass === AssetSubClass.CASH + hasDetails || isCashPosition(holding.assetProfile) ? holding.assetProfile.assetSubClassLabel : undefined, holdings: holding.assetProfile.holdings?.map( diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 02b8479323..431a8da20b 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -6,6 +6,7 @@ import { hasNotDefinedValuesInObject, nullifyValuesInObject } from '@ghostfolio/api/helper/object.helper'; +import { convertValuesToPercentages } from '@ghostfolio/api/helper/portfolio.helper'; import { PerformanceLoggingInterceptor } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor'; import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor'; import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor'; @@ -15,6 +16,7 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/con import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; import { UNKNOWN_KEY } from '@ghostfolio/common/config'; import { SubscriptionType } from '@ghostfolio/common/enums'; +import { isCashPosition } from '@ghostfolio/common/helper'; import { PortfolioDetails, PortfolioDividendsResponse, @@ -44,7 +46,7 @@ import { Version } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; -import { AssetClass, AssetSubClass, DataSource } from '@prisma/client'; +import { DataSource } from '@prisma/client'; import { Big } from 'big.js'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; @@ -128,38 +130,7 @@ export class PortfolioController { !hasScope(impersonationScopes, scopes.portfolioReadValues) || isRestrictedView(this.request.user) ) { - const totalInvestment = holdings - .map(({ investment }) => { - return investment; - }) - .reduce((a, b) => a + b, 0); - - const totalValue = holdings - .filter(({ assetProfile }) => { - return ( - assetProfile.assetClass !== AssetClass.LIQUIDITY && - assetProfile.assetSubClass !== AssetSubClass.CASH - ); - }) - .map(({ valueInBaseCurrency }) => { - return valueInBaseCurrency; - }) - .reduce((a, b) => { - return a + b; - }, 0); - - for (const holding of holdings) { - holding.investment = holding.investment / totalInvestment; - holding.valueInPercentage = holding.valueInBaseCurrency / totalValue; - } - - for (const [name, { valueInBaseCurrency }] of Object.entries(accounts)) { - accounts[name].valueInPercentage = valueInBaseCurrency / totalValue; - } - - for (const [name, { valueInBaseCurrency }] of Object.entries(platforms)) { - platforms[name].valueInPercentage = valueInBaseCurrency / totalValue; - } + convertValuesToPercentages({ accounts, holdings, platforms }); } if ( @@ -208,23 +179,19 @@ export class PortfolioController { assetProfile: { ...portfolioPosition.assetProfile, assetClass: - hasDetails || - portfolioPosition.assetProfile.assetClass === AssetClass.LIQUIDITY + hasDetails || isCashPosition(portfolioPosition.assetProfile) ? portfolioPosition.assetProfile.assetClass : undefined, assetClassLabel: - hasDetails || - portfolioPosition.assetProfile.assetClass === AssetClass.LIQUIDITY + hasDetails || isCashPosition(portfolioPosition.assetProfile) ? portfolioPosition.assetProfile.assetClassLabel : undefined, assetSubClass: - hasDetails || - portfolioPosition.assetProfile.assetSubClass === AssetSubClass.CASH + hasDetails || isCashPosition(portfolioPosition.assetProfile) ? portfolioPosition.assetProfile.assetSubClass : undefined, assetSubClassLabel: - hasDetails || - portfolioPosition.assetProfile.assetSubClass === AssetSubClass.CASH + hasDetails || isCashPosition(portfolioPosition.assetProfile) ? portfolioPosition.assetProfile.assetSubClassLabel : undefined, ...(hasDetails diff --git a/apps/api/src/helper/portfolio.helper.ts b/apps/api/src/helper/portfolio.helper.ts index 6ebe48d3c7..b8152414bb 100644 --- a/apps/api/src/helper/portfolio.helper.ts +++ b/apps/api/src/helper/portfolio.helper.ts @@ -1,5 +1,54 @@ +import { PortfolioDetails } from '@ghostfolio/common/interfaces'; + import { Type as ActivityType } from '@prisma/client'; +export function convertValuesToPercentages({ + accounts, + holdings, + platforms +}: { + accounts: PortfolioDetails['accounts']; + holdings: PortfolioDetails['holdings']; + platforms: PortfolioDetails['platforms']; +}) { + const totalInvestment = holdings + .map(({ investment }) => { + return investment; + }) + .reduce((a, b) => { + return a + b; + }, 0); + + const totalValue = holdings + .map(({ valueInBaseCurrency }) => { + return valueInBaseCurrency; + }) + .reduce((a, b) => { + return a + b; + }, 0); + + for (const holding of holdings) { + holding.investment = totalInvestment + ? holding.investment / totalInvestment + : 0; + holding.valueInPercentage = totalValue + ? holding.valueInBaseCurrency / totalValue + : 0; + } + + for (const account of Object.values(accounts)) { + account.valueInPercentage = totalValue + ? account.valueInBaseCurrency / totalValue + : 0; + } + + for (const platform of Object.values(platforms)) { + platform.valueInPercentage = totalValue + ? platform.valueInBaseCurrency / totalValue + : 0; + } +} + export function getFactor(activityType: ActivityType) { let factor: number; diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html index b59b7e39f9..7e4a0c2f18 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -71,10 +71,10 @@ type="button" [disabled]=" !canDeleteAssetProfile({ - activitiesCount: assetProfile?.activitiesCount, + activitiesCount: assetProfile?.activitiesCount ?? 0, isBenchmark: isBenchmark, symbol: data.symbol, - watchedByCount: assetProfile?.watchedByCount + watchedByCount: assetProfile?.watchedByCount ?? 0 }) " (click)=" @@ -272,8 +272,8 @@ > @if ( - assetProfile?.countries?.length > 0 || - assetProfile?.sectors?.length > 0 + (assetProfile?.countries?.length ?? 0) > 0 || + (assetProfile?.sectors?.length ?? 0) > 0 ) { @if ( assetProfile?.countries?.length === 1 && @@ -660,7 +660,6 @@ formControlName="headers" matInput type="text" - [matAutocomplete]="auto" > diff --git a/apps/client/src/app/components/admin-settings/admin-settings.component.html b/apps/client/src/app/components/admin-settings/admin-settings.component.html index 260aac002e..9d15a6a768 100644 --- a/apps/client/src/app/components/admin-settings/admin-settings.component.html +++ b/apps/client/src/app/components/admin-settings/admin-settings.component.html @@ -230,13 +230,13 @@

Platforms

- +

Tags

- +
diff --git a/apps/client/src/app/components/admin-settings/admin-settings.component.ts b/apps/client/src/app/components/admin-settings/admin-settings.component.ts index 5080e9f856..b0c3dc2d22 100644 --- a/apps/client/src/app/components/admin-settings/admin-settings.component.ts +++ b/apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -3,6 +3,7 @@ import { GfAdminTagComponent } from '@ghostfolio/client/components/admin-tag/adm import { GfDataProviderStatusComponent } from '@ghostfolio/client/components/data-provider-status/data-provider-status.component'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { + DEFAULT_LOCALE, E_MAIL_LINE_BREAK, PROPERTY_API_KEY_GHOSTFOLIO } from '@ghostfolio/common/config'; @@ -98,6 +99,8 @@ export class GfAdminSettingsComponent implements OnInit { public pricingUrl: string; public user: User; + protected readonly DEFAULT_LOCALE = DEFAULT_LOCALE; + public constructor( private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, diff --git a/apps/client/src/app/components/header/header.component.ts b/apps/client/src/app/components/header/header.component.ts index 8e70c1a8c2..d4a2fdbabc 100644 --- a/apps/client/src/app/components/header/header.component.ts +++ b/apps/client/src/app/components/header/header.component.ts @@ -220,7 +220,7 @@ export class GfHeaderComponent implements OnChanges { this.assistentMenuTriggerElement().closeMenu(); } - protected impersonateAccount(aId: string) { + protected impersonateAccount(aId: string | null) { if (aId) { this.impersonationStorageService.setId(aId); } else { diff --git a/apps/client/src/app/components/home-holdings/home-holdings.component.ts b/apps/client/src/app/components/home-holdings/home-holdings.component.ts index ca6351429f..7f02cfb64e 100644 --- a/apps/client/src/app/components/home-holdings/home-holdings.component.ts +++ b/apps/client/src/app/components/home-holdings/home-holdings.component.ts @@ -1,4 +1,5 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; +import { DEFAULT_LOCALE } from '@ghostfolio/common/config'; import { AssetProfileIdentifier, PortfolioPosition, @@ -50,26 +51,29 @@ import { DeviceDetectorService } from 'ngx-device-detector'; export class GfHomeHoldingsComponent implements OnInit { public static DEFAULT_HOLDINGS_VIEW_MODE: HoldingsViewMode = 'TABLE'; + protected readonly DEFAULT_LOCALE = DEFAULT_LOCALE; + protected deviceType: string; protected hasPermissionToAccessHoldingsChart: boolean; protected hasPermissionToCreateActivity: boolean; protected holdings: PortfolioPosition[] | undefined; protected holdingsViewMode: HoldingsViewMode = GfHomeHoldingsComponent.DEFAULT_HOLDINGS_VIEW_MODE; - protected readonly holdingsViewModeOptions: ToggleOption[] = [ - { - iconName: 'reorder-four-outline', - title: $localize`Table`, - value: 'TABLE' - }, - { - iconName: 'grid-outline', - title: $localize`Chart`, - value: 'CHART' - } - ]; + protected readonly holdingsViewModeOptions: ToggleOption[] = + [ + { + iconName: 'reorder-four-outline', + title: $localize`Table`, + value: 'TABLE' + }, + { + iconName: 'grid-outline', + title: $localize`Chart`, + value: 'CHART' + } + ]; protected holdingType: HoldingType = 'ACTIVE'; - protected readonly holdingTypeOptions: ToggleOption[] = [ + protected readonly holdingTypeOptions: ToggleOption[] = [ { label: $localize`Active`, value: 'ACTIVE' }, { label: $localize`Closed`, value: 'CLOSED' } ]; diff --git a/apps/client/src/app/components/home-holdings/home-holdings.html b/apps/client/src/app/components/home-holdings/home-holdings.html index 5c6a95d8e3..df6d57c0e9 100644 --- a/apps/client/src/app/components/home-holdings/home-holdings.html +++ b/apps/client/src/app/components/home-holdings/home-holdings.html @@ -37,11 +37,11 @@ }
- @if (hasPermissionToCreateActivity && holdings?.length > 0) { + @if (hasPermissionToCreateActivity && (holdings?.length ?? 0) > 0) {
>('chartCanvas'); diff --git a/apps/client/src/app/components/markets/markets.component.ts b/apps/client/src/app/components/markets/markets.component.ts index 0317ce6480..3c8fa50f95 100644 --- a/apps/client/src/app/components/markets/markets.component.ts +++ b/apps/client/src/app/components/markets/markets.component.ts @@ -1,4 +1,5 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; +import { DEFAULT_LOCALE } from '@ghostfolio/common/config'; import { resetHours } from '@ghostfolio/common/helper'; import { Benchmark, @@ -43,16 +44,19 @@ import { DeviceDetectorService } from 'ngx-device-detector'; templateUrl: './markets.html' }) export class GfMarketsComponent implements OnInit { + protected readonly DEFAULT_LOCALE = DEFAULT_LOCALE; + protected readonly benchmarks = signal(undefined); protected readonly deviceType = computed( () => this.deviceDetectorService.deviceInfo().deviceType ); - protected readonly fearAndGreedIndexModeOptions: ToggleOption[] = [ - { label: $localize`Stocks`, value: 'STOCKS' }, - { label: $localize`Cryptocurrencies`, value: 'CRYPTOCURRENCIES' } - ]; + protected readonly fearAndGreedIndexModeOptions: ToggleOption[] = + [ + { label: $localize`Stocks`, value: 'STOCKS' }, + { label: $localize`Cryptocurrencies`, value: 'CRYPTOCURRENCIES' } + ]; protected readonly fearLabel = $localize`Fear`; protected readonly greedLabel = $localize`Greed`; diff --git a/apps/client/src/app/components/markets/markets.html b/apps/client/src/app/components/markets/markets.html index d834dfd0e8..68b22d26f0 100644 --- a/apps/client/src/app/components/markets/markets.html +++ b/apps/client/src/app/components/markets/markets.html @@ -52,7 +52,7 @@ diff --git a/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.html b/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.html index d6339a5ffa..8aeb0c4335 100644 --- a/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.html +++ b/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.html @@ -1,7 +1,7 @@
- @if (errors()?.length > 0 && !isLoading()) { + @if ((errors()?.length ?? 0) > 0 && !isLoading()) { Holdings @if ( !hasImpersonationId && - summary?.totalValueInBaseCurrency > 0 && + (summary?.totalValueInBaseCurrency ?? 0) > 0 && user?.settings?.isExperimentalFeatures ) { Cash @if ( !hasImpersonationId && - summary?.totalValueInBaseCurrency > 0 && + (summary?.totalValueInBaseCurrency ?? 0) > 0 && user?.settings?.isExperimentalFeatures ) { Excluded from Analysis @if ( !hasImpersonationId && - summary?.totalValueInBaseCurrency > 0 && + (summary?.totalValueInBaseCurrency ?? 0) > 0 && user?.settings?.isExperimentalFeatures ) { Emergency Fund @if ( !hasImpersonationId && - summary?.totalValueInBaseCurrency > 0 && + (summary?.totalValueInBaseCurrency ?? 0) > 0 && user?.settings?.isExperimentalFeatures ) {

Accounts

@@ -236,7 +238,7 @@ >Other Markets
- @if (markets?.[UNKNOWN_KEY]?.valueInPercentage > 0) { + @if ((markets?.[UNKNOWN_KEY]?.valueInPercentage ?? 0) > 0) {
('month'); - protected readonly modeOptions: ToggleOption[] = [ + protected readonly modeOptions: ToggleOption[] = [ { label: $localize`Monthly`, value: 'month' }, { label: $localize`Yearly`, value: 'year' } ]; diff --git a/apps/client/src/app/pages/public/public-page.component.ts b/apps/client/src/app/pages/public/public-page.component.ts index e76154ce0e..46606753e4 100644 --- a/apps/client/src/app/pages/public/public-page.component.ts +++ b/apps/client/src/app/pages/public/public-page.component.ts @@ -1,7 +1,9 @@ import { UNKNOWN_KEY } from '@ghostfolio/common/config'; import { + convertValuesToPercentagesOfTotal, getAssetProfileIdentifier, - getCountryName + getCountryName, + isCashPosition } from '@ghostfolio/common/helper'; import { InfoItem, @@ -34,9 +36,7 @@ import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatTableDataSource } from '@angular/material/table'; import { ActivatedRoute, Router } from '@angular/router'; -import { AssetClass } from '@prisma/client'; import { StatusCodes } from 'http-status-codes'; -import { isNumber } from 'lodash'; import { DeviceDetectorService } from 'ngx-device-detector'; import { EMPTY } from 'rxjs'; import { catchError } from 'rxjs/operators'; @@ -178,6 +178,8 @@ export class GfPublicPageComponent implements OnInit { } }; + let totalValueExcludingCashPositions = 0; + for (const position of this.publicPortfolioDetails.holdings) { const assetProfileIdentifier = getAssetProfileIdentifier( position.assetProfile @@ -191,39 +193,40 @@ export class GfPublicPageComponent implements OnInit { value: position.allocationInPercentage }; - if (position.assetProfile.assetClass !== AssetClass.LIQUIDITY) { - // Prepare analysis data by continents, countries, holdings and sectors except for liquidity + if (!isCashPosition(position.assetProfile)) { + // Prepare analysis data by continents, countries, holdings and sectors + // except for cash + + const value = position.valueInPercentage ?? 0; + + totalValueExcludingCashPositions += value; if (position.assetProfile.countries.length > 0) { for (const country of position.assetProfile.countries) { const { code, continent, weight } = country; if (this.continents[continent]?.value) { - this.continents[continent].value += - weight * (position.valueInBaseCurrency ?? 0); + this.continents[continent].value += weight * value; } else { this.continents[continent] = { name: translate(continent), - value: weight * (position.valueInBaseCurrency ?? 0) + value: weight * value }; } if (this.countries[code]?.value) { - this.countries[code].value += - weight * (position.valueInBaseCurrency ?? 0); + this.countries[code].value += weight * value; } else { this.countries[code] = { name: getCountryName({ code }), - value: weight * (position.valueInBaseCurrency ?? 0) + value: weight * value }; } } } else { - this.continents[UNKNOWN_KEY].value += - position.valueInBaseCurrency ?? 0; + this.continents[UNKNOWN_KEY].value += value; - this.countries[UNKNOWN_KEY].value += - position.valueInBaseCurrency ?? 0; + this.countries[UNKNOWN_KEY].value += value; } if (position.assetProfile.sectors.length > 0) { @@ -231,25 +234,22 @@ export class GfPublicPageComponent implements OnInit { const { name, weight } = sector; if (this.sectors[name]?.value) { - this.sectors[name].value += - weight * (position.valueInBaseCurrency ?? 0); + this.sectors[name].value += weight * value; } else { this.sectors[name] = { name: translate(name), - value: weight * (position.valueInBaseCurrency ?? 0) + value: weight * value }; } } } else { - this.sectors[UNKNOWN_KEY].value += position.valueInBaseCurrency ?? 0; + this.sectors[UNKNOWN_KEY].value += value; } } const symbol = position.assetProfile.symbol; - const value = isNumber(position.valueInBaseCurrency) - ? position.valueInBaseCurrency - : (position.valueInPercentage ?? 0); + const value = position.valueInPercentage ?? 0; const symbolData = this.symbols[symbol]; @@ -264,5 +264,14 @@ export class GfPublicPageComponent implements OnInit { }; } } + + // The values are percentages of the whole portfolio, but the analysis data + // does not contain the cash positions + for (const values of [this.continents, this.countries, this.sectors]) { + convertValuesToPercentagesOfTotal({ + values, + total: totalValueExcludingCashPositions + }); + } } } diff --git a/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html b/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html index c37abeadd7..c1d0b06126 100644 --- a/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html +++ b/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -75,13 +75,13 @@ matInput readonly type="text" - [(value)]="accessToken" + [value]="accessToken" >