diff --git a/CHANGELOG.md b/CHANGELOG.md index c6f5db1c7..5f01623d1 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 ### Changed +- Simplified the configuration of the benchmarks: `symbolProfileId` instead of `dataSource` and `symbol` - Upgraded `yahoo-finance2` from version `2.3.3` to `2.3.6` ### Fixed diff --git a/apps/api/src/app/benchmark/benchmark.service.ts b/apps/api/src/app/benchmark/benchmark.service.ts index 71337c6ab..7d894428b 100644 --- a/apps/api/src/app/benchmark/benchmark.service.ts +++ b/apps/api/src/app/benchmark/benchmark.service.ts @@ -12,6 +12,7 @@ import { UniqueAsset } from '@ghostfolio/common/interfaces'; import { Injectable } from '@nestjs/common'; +import { SymbolProfile } from '@prisma/client'; import Big from 'big.js'; import { format } from 'date-fns'; import ms from 'ms'; @@ -55,25 +56,25 @@ export class BenchmarkService { } catch {} } - const benchmarkAssets: UniqueAsset[] = - ((await this.propertyService.getByKey( - PROPERTY_BENCHMARKS - )) as UniqueAsset[]) ?? []; + const benchmarkAssetProfiles = await this.getBenchmarkAssetProfiles(); + const promises: Promise[] = []; - const [quotes, assetProfiles] = await Promise.all([ - this.dataProviderService.getQuotes(benchmarkAssets), - this.symbolProfileService.getSymbolProfiles(benchmarkAssets) - ]); + const quotes = await this.dataProviderService.getQuotes( + benchmarkAssetProfiles.map(({ dataSource, symbol }) => { + return { dataSource, symbol }; + }) + ); - for (const benchmarkAsset of benchmarkAssets) { - promises.push(this.marketDataService.getMax(benchmarkAsset)); + for (const { dataSource, symbol } of benchmarkAssetProfiles) { + promises.push(this.marketDataService.getMax({ dataSource, symbol })); } const allTimeHighs = await Promise.all(promises); benchmarks = allTimeHighs.map((allTimeHigh, index) => { - const { marketPrice } = quotes[benchmarkAssets[index].symbol] ?? {}; + const { marketPrice } = + quotes[benchmarkAssetProfiles[index].symbol] ?? {}; let performancePercentFromAllTimeHigh = 0; @@ -88,12 +89,7 @@ export class BenchmarkService { marketCondition: this.getMarketCondition( performancePercentFromAllTimeHigh ), - name: assetProfiles.find(({ dataSource, symbol }) => { - return ( - dataSource === benchmarkAssets[index].dataSource && - symbol === benchmarkAssets[index].symbol - ); - })?.name, + name: benchmarkAssetProfiles[index].name, performances: { allTimeHigh: { performancePercent: performancePercentFromAllTimeHigh @@ -111,19 +107,23 @@ export class BenchmarkService { return benchmarks; } - public async getBenchmarkAssetProfiles(): Promise { - const benchmarkAssets: UniqueAsset[] = - ((await this.propertyService.getByKey( - PROPERTY_BENCHMARKS - )) as UniqueAsset[]) ?? []; + public async getBenchmarkAssetProfiles(): Promise[]> { + const symbolProfileIds: string[] = ( + ((await this.propertyService.getByKey(PROPERTY_BENCHMARKS)) as { + symbolProfileId: string; + }[]) ?? [] + ).map(({ symbolProfileId }) => { + return symbolProfileId; + }); - const assetProfiles = await this.symbolProfileService.getSymbolProfiles( - benchmarkAssets - ); + const assetProfiles = + await this.symbolProfileService.getSymbolProfilesByIds(symbolProfileIds); - return assetProfiles.map(({ dataSource, symbol }) => { + return assetProfiles.map(({ dataSource, id, name, symbol }) => { return { dataSource, + id, + name, symbol }; }); diff --git a/apps/api/src/app/user/update-user-setting.dto.ts b/apps/api/src/app/user/update-user-setting.dto.ts index 0f51f43fd..ea2e784ab 100644 --- a/apps/api/src/app/user/update-user-setting.dto.ts +++ b/apps/api/src/app/user/update-user-setting.dto.ts @@ -1,10 +1,8 @@ -import { UniqueAsset } from '@ghostfolio/common/interfaces'; import type { DateRange, ViewMode } from '@ghostfolio/common/types'; import { IsBoolean, IsIn, IsNumber, - IsObject, IsOptional, IsString } from 'class-validator'; @@ -14,9 +12,9 @@ export class UpdateUserSettingDto { @IsString() baseCurrency?: string; - @IsObject() + @IsString() @IsOptional() - benchmark?: UniqueAsset; + benchmark?: string; @IsIn(['1d', '1y', '5y', 'max', 'ytd']) @IsOptional() diff --git a/apps/api/src/app/user/user.controller.ts b/apps/api/src/app/user/user.controller.ts index 80ac93067..2b1f063bf 100644 --- a/apps/api/src/app/user/user.controller.ts +++ b/apps/api/src/app/user/user.controller.ts @@ -102,10 +102,10 @@ export class UserController { public async updateUserSetting(@Body() data: UpdateUserSettingDto) { if ( size(data) === 1 && - data.dateRange && + (data.benchmark || data.dateRange) && this.request.user.role === 'DEMO' ) { - // Allow date range change for demo user + // Allow benchmark or date range change for demo user } else if ( !hasPermission( this.request.user.permissions, diff --git a/apps/api/src/services/symbol-profile.service.ts b/apps/api/src/services/symbol-profile.service.ts index 1c8da554c..62bc38aab 100644 --- a/apps/api/src/services/symbol-profile.service.ts +++ b/apps/api/src/services/symbol-profile.service.ts @@ -64,6 +64,23 @@ export class SymbolProfileService { .then((symbolProfiles) => this.getSymbols(symbolProfiles)); } + public async getSymbolProfilesByIds( + symbolProfileIds: string[] + ): Promise { + return this.prismaService.symbolProfile + .findMany({ + include: { SymbolProfileOverrides: true }, + where: { + id: { + in: symbolProfileIds.map((symbolProfileId) => { + return symbolProfileId; + }) + } + } + }) + .then((symbolProfiles) => this.getSymbols(symbolProfiles)); + } + /** * @deprecated */ diff --git a/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html b/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html index e8dc9dcdc..2858cb234 100644 --- a/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html +++ b/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html @@ -14,14 +14,13 @@ Compare with... {{ currentBenchmark.symbol }}{{ symbolProfile.name }} diff --git a/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts b/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts index 5d5959cd5..b86cefaae 100644 --- a/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts +++ b/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -39,6 +39,7 @@ import { Tooltip } from 'chart.js'; import annotationPlugin from 'chartjs-plugin-annotation'; +import { SymbolProfile } from '@prisma/client'; @Component({ selector: 'gf-benchmark-comparator', @@ -48,14 +49,14 @@ import annotationPlugin from 'chartjs-plugin-annotation'; }) export class BenchmarkComparatorComponent implements OnChanges, OnDestroy { @Input() benchmarkDataItems: LineChartItem[] = []; - @Input() benchmark: UniqueAsset; - @Input() benchmarks: UniqueAsset[]; + @Input() benchmark: string; + @Input() benchmarks: Partial[]; @Input() daysInMarket: number; @Input() locale: string; @Input() performanceDataItems: LineChartItem[]; @Input() user: User; - @Output() benchmarkChanged = new EventEmitter(); + @Output() benchmarkChanged = new EventEmitter(); @Output() dateRangeChanged = new EventEmitter(); @ViewChild('chartCanvas') chartCanvas; @@ -85,18 +86,8 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy { } } - public compareUniqueAssets( - uniqueAsset1: UniqueAsset, - uniqueAsset2: UniqueAsset - ) { - return ( - uniqueAsset1?.dataSource === uniqueAsset2?.dataSource && - uniqueAsset1?.symbol === uniqueAsset2?.symbol - ); - } - - public onChangeBenchmark(benchmark: UniqueAsset) { - this.benchmarkChanged.next(benchmark); + public onChangeBenchmark(symbolProfileId: string) { + this.benchmarkChanged.next(symbolProfileId); } public onChangeDateRange(dateRange: DateRange) { diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts index bea5d0a9a..29fdb914e 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts @@ -5,11 +5,11 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; import { HistoricalDataItem, Position, - UniqueAsset, User } from '@ghostfolio/common/interfaces'; import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface'; import { DateRange, GroupBy, ToggleOption } from '@ghostfolio/common/types'; +import { SymbolProfile } from '@prisma/client'; import { differenceInDays } from 'date-fns'; import { sortBy } from 'lodash'; import { DeviceDetectorService } from 'ngx-device-detector'; @@ -24,7 +24,7 @@ import { takeUntil } from 'rxjs/operators'; }) export class AnalysisPageComponent implements OnDestroy, OnInit { public benchmarkDataItems: HistoricalDataItem[] = []; - public benchmarks: UniqueAsset[]; + public benchmarks: Partial[]; public bottom3: Position[]; public daysInMarket: number; public deviceType: string; @@ -75,9 +75,9 @@ export class AnalysisPageComponent implements OnDestroy, OnInit { }); } - public onChangeBenchmark(benchmark: UniqueAsset) { + public onChangeBenchmark(symbolProfileId: string) { this.dataService - .putUserSetting({ benchmark }) + .putUserSetting({ benchmark: symbolProfileId }) .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(() => { this.userService.remove(); @@ -179,9 +179,15 @@ export class AnalysisPageComponent implements OnDestroy, OnInit { private updateBenchmarkDataItems() { if (this.user.settings.benchmark) { + const { dataSource, symbol } = + this.benchmarks.find(({ id }) => { + return id === this.user.settings.benchmark; + }) ?? {}; + this.dataService .fetchBenchmarkBySymbol({ - ...this.user.settings.benchmark, + dataSource, + symbol, startDate: this.firstOrderDate }) .pipe(takeUntil(this.unsubscribeSubject)) diff --git a/libs/common/src/lib/interfaces/info-item.interface.ts b/libs/common/src/lib/interfaces/info-item.interface.ts index f2728ae2e..3751c6405 100644 --- a/libs/common/src/lib/interfaces/info-item.interface.ts +++ b/libs/common/src/lib/interfaces/info-item.interface.ts @@ -1,12 +1,11 @@ -import { Tag } from '@prisma/client'; +import { SymbolProfile, Tag } from '@prisma/client'; import { Statistics } from './statistics.interface'; import { Subscription } from './subscription.interface'; -import { UniqueAsset } from './unique-asset.interface'; export interface InfoItem { baseCurrency: string; - benchmarks: UniqueAsset[]; + benchmarks: Partial[]; currencies: string[]; demoAuthToken: string; fearAndGreedDataSource?: string; diff --git a/libs/common/src/lib/interfaces/user-settings.interface.ts b/libs/common/src/lib/interfaces/user-settings.interface.ts index ac120aa15..1237696a4 100644 --- a/libs/common/src/lib/interfaces/user-settings.interface.ts +++ b/libs/common/src/lib/interfaces/user-settings.interface.ts @@ -1,10 +1,8 @@ import { DateRange, ViewMode } from '@ghostfolio/common/types'; -import { UniqueAsset } from './unique-asset.interface'; - export interface UserSettings { baseCurrency?: string; - benchmark?: UniqueAsset; + benchmark?: string; dateRange?: DateRange; emergencyFund?: number; isExperimentalFeatures?: boolean;