diff --git a/apps/api/src/models/portfolio.ts b/apps/api/src/models/portfolio.ts index 00afbc358..4d0c057e5 100644 --- a/apps/api/src/models/portfolio.ts +++ b/apps/api/src/models/portfolio.ts @@ -8,8 +8,10 @@ import { Position, UserWithSettings } from '@ghostfolio/common/interfaces'; +import { Country } from '@ghostfolio/common/interfaces/country.interface'; import { DateRange, OrderWithAccount } from '@ghostfolio/common/types'; import { Prisma } from '@prisma/client'; +import { continents, countries } from 'countries-list'; import { add, format, @@ -207,7 +209,7 @@ export class Portfolio implements PortfolioInterface { symbols.forEach((symbol) => { const accounts: PortfolioPosition['accounts'] = {}; - let countries: Prisma.JsonArray; + let countriesOfSymbol: Country[]; const [portfolioItem] = portfolioItems; const ordersBySymbol = this.getOrders().filter((order) => { @@ -248,9 +250,20 @@ export class Portfolio implements PortfolioInterface { }; } - countries = + countriesOfSymbol = ( (orderOfSymbol.getSymbolProfile()?.countries as Prisma.JsonArray) ?? - []; + [] + ).map((country) => { + const { code, weight } = country as Prisma.JsonObject; + + return { + code: code as string, + continent: + continents[countries[code as string]?.continent] ?? UNKNOWN_KEY, + name: countries[code as string]?.name ?? UNKNOWN_KEY, + weight: weight as number + }; + }); }); let now = portfolioItemsNow.positions[symbol].marketPrice; @@ -288,7 +301,6 @@ export class Portfolio implements PortfolioInterface { details[symbol] = { ...data[symbol], accounts, - countries, symbol, allocationCurrent: this.exchangeRateDataService.toCurrency( @@ -298,6 +310,7 @@ export class Portfolio implements PortfolioInterface { ) / value, allocationInvestment: portfolioItem.positions[symbol].investment / investment, + countries: countriesOfSymbol, grossPerformance: roundTo( portfolioItemsNow.positions[symbol].quantity * (now - before), 2 diff --git a/apps/client/src/app/pages/tools/analysis/analysis-page.component.ts b/apps/client/src/app/pages/tools/analysis/analysis-page.component.ts index bdbda91bb..a01097f0f 100644 --- a/apps/client/src/app/pages/tools/analysis/analysis-page.component.ts +++ b/apps/client/src/app/pages/tools/analysis/analysis-page.component.ts @@ -9,7 +9,6 @@ import { PortfolioPosition, User } from '@ghostfolio/common/interfaces'; -import { Prisma } from '@prisma/client'; import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -23,8 +22,11 @@ export class AnalysisPageComponent implements OnDestroy, OnInit { public accounts: { [symbol: string]: Pick & { value: number }; }; + public continents: { + [code: string]: { name: string; value: number }; + }; public countries: { - [key: string]: { name: string; value: number }; + [code: string]: { name: string; value: number }; }; public deviceType: string; public period = 'current'; @@ -102,6 +104,12 @@ export class AnalysisPageComponent implements OnDestroy, OnInit { aPeriod: string ) { this.accounts = {}; + this.continents = { + [UNKNOWN_KEY]: { + name: UNKNOWN_KEY, + value: 0 + } + }; this.countries = { [UNKNOWN_KEY]: { name: UNKNOWN_KEY, @@ -141,16 +149,28 @@ export class AnalysisPageComponent implements OnDestroy, OnInit { if (position.countries.length > 0) { for (const country of position.countries) { - const { key, weight } = country as Prisma.JsonObject; + const { code, continent, name, weight } = country; + + if (this.continents[continent]?.value) { + this.continents[continent].value += weight * position.value; + } else { + this.continents[continent] = { + name: continent, + value: + weight * + (aPeriod === 'original' + ? this.portfolioPositions[symbol].investment + : this.portfolioPositions[symbol].value) + }; + } - if (this.countries[key]?.value) { - this.countries[key].value += - weight * position.value; + if (this.countries[code]?.value) { + this.countries[code].value += weight * position.value; } else { - this.countries[key] = { - name: key, + this.countries[code] = { + name, value: - weight * + weight * (aPeriod === 'original' ? this.portfolioPositions[symbol].investment : this.portfolioPositions[symbol].value) @@ -158,6 +178,11 @@ export class AnalysisPageComponent implements OnDestroy, OnInit { } } } else { + this.continents[UNKNOWN_KEY].value += + aPeriod === 'original' + ? this.portfolioPositions[symbol].investment + : this.portfolioPositions[symbol].value; + this.countries[UNKNOWN_KEY].value += aPeriod === 'original' ? this.portfolioPositions[symbol].investment diff --git a/apps/client/src/app/pages/tools/analysis/analysis-page.html b/apps/client/src/app/pages/tools/analysis/analysis-page.html index 272dbc864..0b0ff2310 100644 --- a/apps/client/src/app/pages/tools/analysis/analysis-page.html +++ b/apps/client/src/app/pages/tools/analysis/analysis-page.html @@ -105,7 +105,7 @@
- By Currency + By Continent @@ -146,6 +146,28 @@
+
+ + + By Currency + + + + + + +
diff --git a/libs/common/src/lib/interfaces/country.interface.ts b/libs/common/src/lib/interfaces/country.interface.ts new file mode 100644 index 000000000..4119d91ea --- /dev/null +++ b/libs/common/src/lib/interfaces/country.interface.ts @@ -0,0 +1,6 @@ +export interface Country { + code: string; + continent: string; + name: string; + weight: number; +} diff --git a/libs/common/src/lib/interfaces/portfolio-position.interface.ts b/libs/common/src/lib/interfaces/portfolio-position.interface.ts index dba9152f2..ad120cbf4 100644 --- a/libs/common/src/lib/interfaces/portfolio-position.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-position.interface.ts @@ -1,5 +1,7 @@ import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces'; -import { Currency, Prisma } from '@prisma/client'; +import { Currency } from '@prisma/client'; + +import { Country } from './country.interface'; export interface PortfolioPosition { accounts: { @@ -7,7 +9,7 @@ export interface PortfolioPosition { }; allocationCurrent: number; allocationInvestment: number; - countries: Prisma.JsonArray; + countries: Country[]; currency: Currency; exchange?: string; grossPerformance: number; diff --git a/package.json b/package.json index 24524a2cd..870e98cfc 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "cheerio": "1.0.0-rc.6", "class-transformer": "0.3.2", "class-validator": "0.13.1", + "countries-list": "2.6.1", "countup.js": "2.0.7", "cryptocurrencies": "7.0.0", "date-fns": "2.19.0", diff --git a/yarn.lock b/yarn.lock index 08e785ae6..99efb34ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4698,6 +4698,11 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" +countries-list@2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/countries-list/-/countries-list-2.6.1.tgz#d479757ac873b1e596ccea0a925962d20396c0cb" + integrity sha512-jXM1Nv3U56dPQ1DsUSsEaGmLHburo4fnB7m+1yhWDUVvx5gXCd1ok/y3gXCjXzhqyawG+igcPYcAl4qjkvopaQ== + countup.js@2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/countup.js/-/countup.js-2.0.7.tgz#56b72a87fc0ee3cadb38356c246ccac88fb0a8cc"