Feature/simplify benchmark configuration (#1248)
* Simplify benchmark configuration * Update changelog
This commit is contained in:
parent
0fcfa6c1bd
commit
e320aa91f7
@ -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
|
||||
|
@ -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<number>[] = [];
|
||||
|
||||
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<UniqueAsset[]> {
|
||||
const benchmarkAssets: UniqueAsset[] =
|
||||
((await this.propertyService.getByKey(
|
||||
PROPERTY_BENCHMARKS
|
||||
)) as UniqueAsset[]) ?? [];
|
||||
public async getBenchmarkAssetProfiles(): Promise<Partial<SymbolProfile>[]> {
|
||||
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
|
||||
};
|
||||
});
|
||||
|
@ -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(<DateRange[]>['1d', '1y', '5y', 'max', 'ytd'])
|
||||
@IsOptional()
|
||||
|
@ -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,
|
||||
|
@ -64,6 +64,23 @@ export class SymbolProfileService {
|
||||
.then((symbolProfiles) => this.getSymbols(symbolProfiles));
|
||||
}
|
||||
|
||||
public async getSymbolProfilesByIds(
|
||||
symbolProfileIds: string[]
|
||||
): Promise<EnhancedSymbolProfile[]> {
|
||||
return this.prismaService.symbolProfile
|
||||
.findMany({
|
||||
include: { SymbolProfileOverrides: true },
|
||||
where: {
|
||||
id: {
|
||||
in: symbolProfileIds.map((symbolProfileId) => {
|
||||
return symbolProfileId;
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((symbolProfiles) => this.getSymbols(symbolProfiles));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
|
@ -14,14 +14,13 @@
|
||||
<mat-label i18n>Compare with...</mat-label>
|
||||
<mat-select
|
||||
name="benchmark"
|
||||
[compareWith]="compareUniqueAssets"
|
||||
[value]="benchmark"
|
||||
(selectionChange)="onChangeBenchmark($event.value)"
|
||||
>
|
||||
<mat-option
|
||||
*ngFor="let currentBenchmark of benchmarks"
|
||||
[value]="currentBenchmark"
|
||||
>{{ currentBenchmark.symbol }}</mat-option
|
||||
*ngFor="let symbolProfile of benchmarks"
|
||||
[value]="symbolProfile.id"
|
||||
>{{ symbolProfile.name }}</mat-option
|
||||
>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
@ -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<SymbolProfile>[];
|
||||
@Input() daysInMarket: number;
|
||||
@Input() locale: string;
|
||||
@Input() performanceDataItems: LineChartItem[];
|
||||
@Input() user: User;
|
||||
|
||||
@Output() benchmarkChanged = new EventEmitter<UniqueAsset>();
|
||||
@Output() benchmarkChanged = new EventEmitter<string>();
|
||||
@Output() dateRangeChanged = new EventEmitter<DateRange>();
|
||||
|
||||
@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) {
|
||||
|
@ -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<SymbolProfile>[];
|
||||
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))
|
||||
|
@ -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<SymbolProfile>[];
|
||||
currencies: string[];
|
||||
demoAuthToken: string;
|
||||
fearAndGreedDataSource?: string;
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user