diff --git a/CHANGELOG.md b/CHANGELOG.md index e57661406..2034a8f9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added the calculation for developed vs. emerging markets to the allocations page - Added a hover effect to the page tabs - Extended the feature overview page by _Bonds_ and _Emergency Fund_ diff --git a/apps/api/src/app/portfolio/portfolio-service.strategy.ts b/apps/api/src/app/portfolio/portfolio-service.strategy.ts index 49ec0422f..a85b28852 100644 --- a/apps/api/src/app/portfolio/portfolio-service.strategy.ts +++ b/apps/api/src/app/portfolio/portfolio-service.strategy.ts @@ -13,8 +13,9 @@ export class PortfolioServiceStrategy { @Inject(REQUEST) private readonly request: RequestWithUser ) {} - public get() { + public get(newCalculationEngine?: boolean) { if ( + newCalculationEngine || this.request.user?.Settings?.settings?.['isNewCalculationEngine'] === true ) { return this.portfolioServiceNew; diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 2d7993859..1bb42a0ed 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -120,7 +120,7 @@ export class PortfolioController { const { accounts, holdings, hasErrors } = await this.portfolioServiceStrategy - .get() + .get(true) .getDetails(impersonationId, this.request.user.id, range); if (hasErrors || hasNotDefinedValuesInObject(holdings)) { @@ -277,7 +277,7 @@ export class PortfolioController { } const { holdings } = await this.portfolioServiceStrategy - .get() + .get(true) .getDetails(access.userId, access.userId); const portfolioPublicDetails: PortfolioPublicDetails = { @@ -304,6 +304,7 @@ export class PortfolioController { allocationCurrent: portfolioPosition.allocationCurrent, countries: hasDetails ? portfolioPosition.countries : [], currency: portfolioPosition.currency, + markets: portfolioPosition.markets, name: portfolioPosition.name, sectors: hasDetails ? portfolioPosition.sectors : [], value: portfolioPosition.value / totalValue diff --git a/apps/api/src/app/portfolio/portfolio.service-new.ts b/apps/api/src/app/portfolio/portfolio.service-new.ts index 2ea49de8f..63b8544a8 100644 --- a/apps/api/src/app/portfolio/portfolio.service-new.ts +++ b/apps/api/src/app/portfolio/portfolio.service-new.ts @@ -40,6 +40,7 @@ import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.in import type { AccountWithValue, DateRange, + Market, OrderWithAccount, RequestWithUser } from '@ghostfolio/common/types'; @@ -71,6 +72,9 @@ import { import { PortfolioCalculatorNew } from './portfolio-calculator-new'; import { RulesService } from './rules.service'; +const developedMarkets = require('../../assets/countries/developed-markets.json'); +const emergingMarkets = require('../../assets/countries/emerging-markets.json'); + @Injectable() export class PortfolioServiceNew { public constructor( @@ -380,7 +384,31 @@ export class PortfolioServiceNew { const value = item.quantity.mul(item.marketPrice); const symbolProfile = symbolProfileMap[item.symbol]; const dataProviderResponse = dataProviderResponses[item.symbol]; + + const markets: { [key in Market]: number } = { + developedMarkets: 0, + emergingMarkets: 0, + otherMarkets: 0 + }; + + for (const country of symbolProfile.countries) { + if (developedMarkets.includes(country.code)) { + markets.developedMarkets = new Big(markets.developedMarkets) + .plus(country.weight) + .toNumber(); + } else if (emergingMarkets.includes(country.code)) { + markets.emergingMarkets = new Big(markets.emergingMarkets) + .plus(country.weight) + .toNumber(); + } else { + markets.otherMarkets = new Big(markets.otherMarkets) + .plus(country.weight) + .toNumber(); + } + } + holdings[item.symbol] = { + markets, allocationCurrent: value.div(totalValue).toNumber(), allocationInvestment: item.investment.div(totalInvestment).toNumber(), assetClass: symbolProfile.assetClass, diff --git a/apps/api/src/assets/countries/developed-markets.json b/apps/api/src/assets/countries/developed-markets.json new file mode 100644 index 000000000..5e281d475 --- /dev/null +++ b/apps/api/src/assets/countries/developed-markets.json @@ -0,0 +1,26 @@ +[ + "AT", + "AU", + "BE", + "CA", + "CH", + "DE", + "DK", + "ES", + "FI", + "FR", + "GB", + "HK", + "IE", + "IL", + "IT", + "JP", + "LU", + "NL", + "NO", + "NZ", + "PT", + "SE", + "SG", + "US" +] diff --git a/apps/api/src/assets/countries/emerging-markets.json b/apps/api/src/assets/countries/emerging-markets.json new file mode 100644 index 000000000..328187964 --- /dev/null +++ b/apps/api/src/assets/countries/emerging-markets.json @@ -0,0 +1,28 @@ +[ + "AE", + "BR", + "CL", + "CN", + "CO", + "CY", + "CZ", + "EG", + "GR", + "HK", + "HU", + "ID", + "IN", + "KR", + "KW", + "MX", + "MY", + "PE", + "PH", + "PL", + "QA", + "SA", + "TH", + "TR", + "TW", + "ZA" +] diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts index 2ca5f6930..5d7be99c4 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts @@ -14,7 +14,7 @@ import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; -import { ToggleOption } from '@ghostfolio/common/types'; +import { Market, ToggleOption } from '@ghostfolio/common/types'; import { Account, AssetClass, DataSource } from '@prisma/client'; import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject, Subscription } from 'rxjs'; @@ -42,6 +42,9 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { public deviceType: string; public hasImpersonationId: boolean; public hasPermissionToCreateOrder: boolean; + public markets: { + [key in Market]: { name: string; value: number }; + }; public period = 'current'; public periodOptions: ToggleOption[] = [ { label: 'Initial', value: 'original' }, @@ -160,6 +163,20 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { value: 0 } }; + this.markets = { + developedMarkets: { + name: 'developedMarkets', + value: 0 + }, + emergingMarkets: { + name: 'emergingMarkets', + value: 0 + }, + otherMarkets: { + name: 'otherMarkets', + value: 0 + } + }; this.positions = {}; this.positionsArray = []; this.sectors = { @@ -219,6 +236,16 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { // Prepare analysis data by continents, countries and sectors except for cash if (position.countries.length > 0) { + this.markets.developedMarkets.value += + position.markets.developedMarkets * + (aPeriod === 'original' ? position.investment : position.value); + this.markets.emergingMarkets.value += + position.markets.emergingMarkets * + (aPeriod === 'original' ? position.investment : position.value); + this.markets.otherMarkets.value += + position.markets.otherMarkets * + (aPeriod === 'original' ? position.investment : position.value); + for (const country of position.countries) { const { code, continent, name, weight } = country; @@ -294,6 +321,18 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { }; } } + + const marketsTotal = + this.markets.developedMarkets.value + + this.markets.emergingMarkets.value + + this.markets.otherMarkets.value; + + this.markets.developedMarkets.value = + this.markets.developedMarkets.value / marketsTotal; + this.markets.emergingMarkets.value = + this.markets.emergingMarkets.value / marketsTotal; + this.markets.otherMarkets.value = + this.markets.otherMarkets.value / marketsTotal; } public onChangePeriod(aValue: string) { diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.html b/apps/client/src/app/pages/portfolio/allocations/allocations-page.html index 276de32ce..679570998 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.html +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.html @@ -190,6 +190,32 @@ [countries]="countries" [isInPercent]="hasImpersonationId || user.settings.isRestrictedView" > +