Feature/extend watchlist endpoint by name, performances and market condition (#4634)
* Extend watchlist endpoint by name, performances and market condition * Update changelog
This commit is contained in:
parent
6bb85c4fb8
commit
770b322137
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Extended the watchlist by the date of the last all time high, the current change to the all time high and the current market condition (experimental)
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved the language localization for Français (`fr`)
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
|
||||
import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module';
|
||||
import { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.module';
|
||||
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
||||
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
|
||||
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
|
||||
import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
|
||||
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
|
||||
@ -13,8 +15,10 @@ import { WatchlistService } from './watchlist.service';
|
||||
@Module({
|
||||
controllers: [WatchlistController],
|
||||
imports: [
|
||||
BenchmarkModule,
|
||||
DataGatheringModule,
|
||||
DataProviderModule,
|
||||
MarketDataModule,
|
||||
PrismaModule,
|
||||
SymbolProfileModule,
|
||||
TransformDataSourceInRequestModule,
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.service';
|
||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
|
||||
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
||||
import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
|
||||
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
|
||||
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
|
||||
import { WatchlistResponse } from '@ghostfolio/common/interfaces';
|
||||
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { DataSource, Prisma } from '@prisma/client';
|
||||
@ -10,8 +12,10 @@ import { DataSource, Prisma } from '@prisma/client';
|
||||
@Injectable()
|
||||
export class WatchlistService {
|
||||
public constructor(
|
||||
private readonly benchmarkService: BenchmarkService,
|
||||
private readonly dataGatheringService: DataGatheringService,
|
||||
private readonly dataProviderService: DataProviderService,
|
||||
private readonly marketDataService: MarketDataService,
|
||||
private readonly prismaService: PrismaService,
|
||||
private readonly symbolProfileService: SymbolProfileService
|
||||
) {}
|
||||
@ -87,7 +91,7 @@ export class WatchlistService {
|
||||
|
||||
public async getWatchlistItems(
|
||||
userId: string
|
||||
): Promise<AssetProfileIdentifier[]> {
|
||||
): Promise<WatchlistResponse['watchlist']> {
|
||||
const user = await this.prismaService.user.findUnique({
|
||||
select: {
|
||||
watchlist: {
|
||||
@ -97,6 +101,50 @@ export class WatchlistService {
|
||||
where: { id: userId }
|
||||
});
|
||||
|
||||
return user.watchlist ?? [];
|
||||
const [assetProfiles, quotes] = await Promise.all([
|
||||
this.symbolProfileService.getSymbolProfiles(user.watchlist),
|
||||
this.dataProviderService.getQuotes({
|
||||
items: user.watchlist.map(({ dataSource, symbol }) => {
|
||||
return { dataSource, symbol };
|
||||
})
|
||||
})
|
||||
]);
|
||||
|
||||
const watchlist = await Promise.all(
|
||||
user.watchlist.map(async ({ dataSource, symbol }) => {
|
||||
const assetProfile = assetProfiles.find((profile) => {
|
||||
return profile.dataSource === dataSource && profile.symbol === symbol;
|
||||
});
|
||||
|
||||
const allTimeHigh = await this.marketDataService.getMax({
|
||||
dataSource,
|
||||
symbol
|
||||
});
|
||||
|
||||
const performancePercent =
|
||||
this.benchmarkService.calculateChangeInPercentage(
|
||||
allTimeHigh?.marketPrice,
|
||||
quotes[symbol]?.marketPrice
|
||||
);
|
||||
|
||||
return {
|
||||
dataSource,
|
||||
symbol,
|
||||
marketCondition:
|
||||
this.benchmarkService.getMarketCondition(performancePercent),
|
||||
name: assetProfile?.name,
|
||||
performances: {
|
||||
allTimeHigh: {
|
||||
performancePercent,
|
||||
date: allTimeHigh?.date
|
||||
}
|
||||
}
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return watchlist.sort((a, b) => {
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -212,6 +212,18 @@ export class BenchmarkService {
|
||||
};
|
||||
}
|
||||
|
||||
public getMarketCondition(
|
||||
aPerformanceInPercent: number
|
||||
): Benchmark['marketCondition'] {
|
||||
if (aPerformanceInPercent >= 0) {
|
||||
return 'ALL_TIME_HIGH';
|
||||
} else if (aPerformanceInPercent <= -0.2) {
|
||||
return 'BEAR_MARKET';
|
||||
} else {
|
||||
return 'NEUTRAL_MARKET';
|
||||
}
|
||||
}
|
||||
|
||||
private async calculateAndCacheBenchmarks({
|
||||
enableSharing = false
|
||||
}): Promise<BenchmarkResponse['benchmarks']> {
|
||||
@ -302,16 +314,4 @@ export class BenchmarkService {
|
||||
|
||||
return benchmarks;
|
||||
}
|
||||
|
||||
private getMarketCondition(
|
||||
aPerformanceInPercent: number
|
||||
): Benchmark['marketCondition'] {
|
||||
if (aPerformanceInPercent >= 0) {
|
||||
return 'ALL_TIME_HIGH';
|
||||
} else if (aPerformanceInPercent <= -0.2) {
|
||||
return 'BEAR_MARKET';
|
||||
} else {
|
||||
return 'NEUTRAL_MARKET';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
User
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||
import { BenchmarkTrend } from '@ghostfolio/common/types';
|
||||
import { GfBenchmarkComponent } from '@ghostfolio/ui/benchmark';
|
||||
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
|
||||
|
||||
@ -118,15 +119,17 @@ export class HomeWatchlistComponent implements OnDestroy, OnInit {
|
||||
.fetchWatchlist()
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(({ watchlist }) => {
|
||||
this.watchlist = watchlist.map(({ dataSource, symbol }) => ({
|
||||
dataSource,
|
||||
symbol,
|
||||
marketCondition: null,
|
||||
name: symbol,
|
||||
performances: null,
|
||||
trend50d: 'UNKNOWN',
|
||||
trend200d: 'UNKNOWN'
|
||||
}));
|
||||
this.watchlist = watchlist.map(
|
||||
({ dataSource, marketCondition, name, performances, symbol }) => ({
|
||||
dataSource,
|
||||
marketCondition,
|
||||
name,
|
||||
performances,
|
||||
symbol,
|
||||
trend50d: 'UNKNOWN' as BenchmarkTrend,
|
||||
trend200d: 'UNKNOWN' as BenchmarkTrend
|
||||
})
|
||||
);
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
});
|
||||
|
@ -7,7 +7,7 @@
|
||||
}
|
||||
</span>
|
||||
</h1>
|
||||
<div class="mb-3 row">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-8 offset-md-2">
|
||||
<gf-benchmark
|
||||
[benchmarks]="watchlist"
|
||||
|
@ -1,5 +1,12 @@
|
||||
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
|
||||
import {
|
||||
AssetProfileIdentifier,
|
||||
Benchmark
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
|
||||
export interface WatchlistResponse {
|
||||
watchlist: AssetProfileIdentifier[];
|
||||
watchlist: (AssetProfileIdentifier & {
|
||||
marketCondition: Benchmark['marketCondition'];
|
||||
name: string;
|
||||
performances: Benchmark['performances'];
|
||||
})[];
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user