diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ce9598e3..b2b2eb1ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Included asset profile data in the endpoint `GET api/v1/portfolio/holdings` +- Included asset profile data in the holdings of the public page - Reused the value component in the platform management of the admin control panel - Reused the value component in the tag management of the admin control panel - Upgraded `jsonpath` from version `1.1.1` to `1.2.1` diff --git a/apps/api/src/app/endpoints/public/public.controller.ts b/apps/api/src/app/endpoints/public/public.controller.ts index b4ecd37ba..a1b2b7864 100644 --- a/apps/api/src/app/endpoints/public/public.controller.ts +++ b/apps/api/src/app/endpoints/public/public.controller.ts @@ -167,6 +167,7 @@ export class PublicController { allocationInPercentage: portfolioPosition.valueInBaseCurrency / totalValue, assetClass: hasDetails ? portfolioPosition.assetClass : undefined, + assetProfile: hasDetails ? portfolioPosition.assetProfile : undefined, countries: hasDetails ? portfolioPosition.countries : [], currency: hasDetails ? portfolioPosition.currency : undefined, dataSource: portfolioPosition.dataSource, diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 26d16bf75..1bfad8395 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -623,6 +623,27 @@ export class PortfolioService { ? 0 : valueInBaseCurrency.div(filteredValueInBaseCurrency).toNumber(), assetClass: assetProfile.assetClass, + assetProfile: { + assetClass: assetProfile.assetClass, + assetSubClass: assetProfile.assetSubClass, + countries: assetProfile.countries, + dataSource: assetProfile.dataSource, + holdings: assetProfile.holdings.map( + ({ allocationInPercentage, name }) => { + return { + allocationInPercentage, + name, + valueInBaseCurrency: valueInBaseCurrency + .mul(allocationInPercentage) + .toNumber() + }; + } + ), + name: assetProfile.name, + sectors: assetProfile.sectors, + symbol: assetProfile.symbol, + url: assetProfile.url + }, assetSubClass: assetProfile.assetSubClass, countries: assetProfile.countries, dataSource: assetProfile.dataSource, @@ -1672,6 +1693,16 @@ export class PortfolioService { allocationInPercentage: 0, assetClass: AssetClass.LIQUIDITY, assetSubClass: AssetSubClass.CASH, + assetProfile: { + assetClass: AssetClass.LIQUIDITY, + assetSubClass: AssetSubClass.CASH, + countries: [], + dataSource: undefined, + holdings: [], + name: currency, + sectors: [], + symbol: currency + }, countries: [], dataSource: undefined, dateOfFirstActivity: undefined, diff --git a/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts b/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts index 3cccd6efa..57643f76c 100644 --- a/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts +++ b/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts @@ -68,6 +68,7 @@ export class TransformDataSourceInResponseInterceptor< 'errors[*].dataSource', 'fearAndGreedIndex.CRYPTOCURRENCIES.dataSource', 'fearAndGreedIndex.STOCKS.dataSource', + 'holdings[*].assetProfile.dataSource', 'holdings[*].dataSource', 'items[*].dataSource', 'SymbolProfile.dataSource', diff --git a/libs/common/src/lib/interfaces/portfolio-position.interface.ts b/libs/common/src/lib/interfaces/portfolio-position.interface.ts index 620cc00e9..742e63d7a 100644 --- a/libs/common/src/lib/interfaces/portfolio-position.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-position.interface.ts @@ -3,19 +3,50 @@ import { Market, MarketAdvanced } from '@ghostfolio/common/types'; import { AssetClass, AssetSubClass, DataSource, Tag } from '@prisma/client'; import { Country } from './country.interface'; +import { EnhancedSymbolProfile } from './enhanced-symbol-profile.interface'; import { Holding } from './holding.interface'; import { Sector } from './sector.interface'; export interface PortfolioPosition { activitiesCount: number; allocationInPercentage: number; + + /** @deprecated */ assetClass?: AssetClass; + + /** @deprecated */ assetClassLabel?: string; + + assetProfile: Pick< + EnhancedSymbolProfile, + | 'assetClass' + | 'assetSubClass' + | 'countries' + | 'dataSource' + | 'holdings' + | 'name' + | 'sectors' + | 'symbol' + | 'url' + > & { + assetClassLabel?: string; + assetSubClassLabel?: string; + }; + + /** @deprecated */ assetSubClass?: AssetSubClass; + + /** @deprecated */ assetSubClassLabel?: string; + + /** @deprecated */ countries: Country[]; + currency: string; + + /** @deprecated */ dataSource: DataSource; + dateOfFirstActivity: Date; dividend: number; exchange?: string; @@ -23,24 +54,38 @@ export interface PortfolioPosition { grossPerformancePercent: number; grossPerformancePercentWithCurrencyEffect: number; grossPerformanceWithCurrencyEffect: number; + + /** @deprecated */ holdings: Holding[]; + investment: number; marketChange?: number; marketChangePercent?: number; marketPrice: number; markets?: { [key in Market]: number }; marketsAdvanced?: { [key in MarketAdvanced]: number }; + + /** @deprecated */ name: string; + netPerformance: number; netPerformancePercent: number; netPerformancePercentWithCurrencyEffect: number; netPerformanceWithCurrencyEffect: number; quantity: number; + + /** @deprecated */ sectors: Sector[]; + + /** @deprecated */ symbol: string; + tags?: Tag[]; type?: string; + + /** @deprecated */ url?: string; + valueInBaseCurrency?: number; valueInPercentage?: number; } diff --git a/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts b/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts index 4a087ad16..eae14cec6 100644 --- a/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts @@ -14,16 +14,31 @@ export interface PublicPortfolioResponse extends PublicPortfolioResponseV1 { [symbol: string]: Pick< PortfolioPosition, | 'allocationInPercentage' + + /** @deprecated */ | 'assetClass' + | 'assetProfile' + + /** @deprecated */ | 'countries' | 'currency' + + /** @deprecated */ | 'dataSource' | 'dateOfFirstActivity' | 'markets' + + /** @deprecated */ | 'name' | 'netPerformancePercentWithCurrencyEffect' + + /** @deprecated */ | 'sectors' + + /** @deprecated */ | 'symbol' + + /** @deprecated */ | 'url' | 'valueInBaseCurrency' | 'valueInPercentage' diff --git a/libs/ui/src/lib/mocks/holdings.ts b/libs/ui/src/lib/mocks/holdings.ts index 5ab3e89eb..f426593c1 100644 --- a/libs/ui/src/lib/mocks/holdings.ts +++ b/libs/ui/src/lib/mocks/holdings.ts @@ -6,14 +6,35 @@ export const holdings: PortfolioPosition[] = [ allocationInPercentage: 0.042990776363386086, assetClass: 'EQUITY', assetClassLabel: 'Equity', + assetProfile: { + assetClass: 'EQUITY', + assetSubClass: 'STOCK', + countries: [ + { + code: 'US', + continent: 'North America', + name: 'United States', + weight: 1 + } + ], + dataSource: 'YAHOO', + holdings: [], + sectors: [ + { + name: 'Technology', + weight: 1 + } + ], + symbol: 'AAPL' + }, assetSubClass: 'STOCK', assetSubClassLabel: 'Stock', countries: [ { code: 'US', - weight: 1, continent: 'North America', - name: 'United States' + name: 'United States', + weight: 1 } ], currency: 'USD', @@ -49,14 +70,35 @@ export const holdings: PortfolioPosition[] = [ allocationInPercentage: 0.02377401948293552, assetClass: 'EQUITY', assetClassLabel: 'Equity', + assetProfile: { + assetClass: 'EQUITY', + assetSubClass: 'STOCK', + countries: [ + { + code: 'DE', + continent: 'Europe', + name: 'Germany', + weight: 1 + } + ], + dataSource: 'YAHOO', + holdings: [], + sectors: [ + { + name: 'Financial Services', + weight: 1 + } + ], + symbol: 'ALV.DE' + }, assetSubClass: 'STOCK', assetSubClassLabel: 'Stock', countries: [ { code: 'DE', - weight: 1, continent: 'Europe', - name: 'Germany' + name: 'Germany', + weight: 1 } ], currency: 'EUR', @@ -92,14 +134,35 @@ export const holdings: PortfolioPosition[] = [ allocationInPercentage: 0.08038536990007467, assetClass: 'EQUITY', assetClassLabel: 'Equity', + assetProfile: { + assetClass: 'EQUITY', + assetSubClass: 'STOCK', + countries: [ + { + code: 'US', + continent: 'North America', + name: 'United States', + weight: 1 + } + ], + dataSource: 'YAHOO', + holdings: [], + sectors: [ + { + name: 'Consumer Discretionary', + weight: 1 + } + ], + symbol: 'AMZN' + }, assetSubClass: 'STOCK', assetSubClassLabel: 'Stock', countries: [ { code: 'US', - weight: 1, continent: 'North America', - name: 'United States' + name: 'United States', + weight: 1 } ], currency: 'USD', @@ -135,6 +198,15 @@ export const holdings: PortfolioPosition[] = [ allocationInPercentage: 0.19216416482928922, assetClass: 'LIQUIDITY', assetClassLabel: 'Liquidity', + assetProfile: { + assetClass: 'LIQUIDITY', + assetSubClass: 'CASH', + countries: [], + dataSource: 'COINGECKO', + holdings: [], + sectors: [], + symbol: 'bitcoin' + }, assetSubClass: 'CRYPTOCURRENCY', assetSubClassLabel: 'Cryptocurrency', countries: [], @@ -166,14 +238,35 @@ export const holdings: PortfolioPosition[] = [ allocationInPercentage: 0.04307127421937313, assetClass: 'EQUITY', assetClassLabel: 'Equity', + assetProfile: { + assetClass: 'EQUITY', + assetSubClass: 'STOCK', + countries: [ + { + code: 'US', + continent: 'North America', + name: 'United States', + weight: 1 + } + ], + dataSource: 'YAHOO', + holdings: [], + sectors: [ + { + name: 'Technology', + weight: 1 + } + ], + symbol: 'MSFT' + }, assetSubClass: 'STOCK', assetSubClassLabel: 'Stock', countries: [ { code: 'US', - weight: 1, continent: 'North America', - name: 'United States' + name: 'United States', + weight: 1 } ], currency: 'USD', @@ -209,14 +302,35 @@ export const holdings: PortfolioPosition[] = [ allocationInPercentage: 0.18762679306394897, assetClass: 'EQUITY', assetClassLabel: 'Equity', + assetProfile: { + assetClass: 'EQUITY', + assetSubClass: 'STOCK', + countries: [ + { + code: 'US', + continent: 'North America', + name: 'United States', + weight: 1 + } + ], + dataSource: 'YAHOO', + holdings: [], + sectors: [ + { + name: 'Consumer Discretionary', + weight: 1 + } + ], + symbol: 'TSLA' + }, assetSubClass: 'STOCK', assetSubClassLabel: 'Stock', countries: [ { code: 'US', - weight: 1, continent: 'North America', - name: 'United States' + name: 'United States', + weight: 1 } ], currency: 'USD', @@ -252,6 +366,27 @@ export const holdings: PortfolioPosition[] = [ allocationInPercentage: 0.053051250766657634, assetClass: 'EQUITY', assetClassLabel: 'Equity', + assetProfile: { + assetClass: 'EQUITY', + assetSubClass: 'ETF', + countries: [ + { + code: 'US', + continent: 'North America', + name: 'United States', + weight: 1 + } + ], + dataSource: 'YAHOO', + holdings: [], + sectors: [ + { + name: 'Equity', + weight: 1 + } + ], + symbol: 'VTI' + }, assetSubClass: 'ETF', assetSubClassLabel: 'ETF', countries: [