diff --git a/CHANGELOG.md b/CHANGELOG.md index dfd347337..9cc1bca12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added the _Ghostfolio_ trailer to the landing page +- Extended the markets overview by benchmarks (current change to the all time high) ## 1.151.0 - 24.05.2022 diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index f3b85fc9b..f95c93fd2 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -20,6 +20,7 @@ import { AccountModule } from './account/account.module'; import { AdminModule } from './admin/admin.module'; import { AppController } from './app.controller'; import { AuthModule } from './auth/auth.module'; +import { BenchmarkModule } from './benchmark/benchmark.module'; import { CacheModule } from './cache/cache.module'; import { ExportModule } from './export/export.module'; import { ImportModule } from './import/import.module'; @@ -37,6 +38,7 @@ import { UserModule } from './user/user.module'; AccountModule, AuthDeviceModule, AuthModule, + BenchmarkModule, BullModule.forRoot({ redis: { host: process.env.REDIS_HOST, diff --git a/apps/api/src/app/benchmark/benchmark.controller.ts b/apps/api/src/app/benchmark/benchmark.controller.ts new file mode 100644 index 000000000..86bbf9d36 --- /dev/null +++ b/apps/api/src/app/benchmark/benchmark.controller.ts @@ -0,0 +1,32 @@ +import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor'; +import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor'; +import { PropertyService } from '@ghostfolio/api/services/property/property.service'; +import { PROPERTY_BENCHMARKS } from '@ghostfolio/common/config'; +import { BenchmarkResponse, UniqueAsset } from '@ghostfolio/common/interfaces'; +import { Controller, Get, UseGuards, UseInterceptors } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +import { BenchmarkService } from './benchmark.service'; + +@Controller('benchmark') +export class BenchmarkController { + public constructor( + private readonly benchmarkService: BenchmarkService, + private readonly propertyService: PropertyService + ) {} + + @Get() + @UseGuards(AuthGuard('jwt')) + @UseInterceptors(TransformDataSourceInRequestInterceptor) + @UseInterceptors(TransformDataSourceInResponseInterceptor) + public async getBenchmark(): Promise { + const benchmarkAssets: UniqueAsset[] = + ((await this.propertyService.getByKey( + PROPERTY_BENCHMARKS + )) as UniqueAsset[]) ?? []; + + return { + benchmarks: await this.benchmarkService.getBenchmarks(benchmarkAssets) + }; + } +} diff --git a/apps/api/src/app/benchmark/benchmark.module.ts b/apps/api/src/app/benchmark/benchmark.module.ts new file mode 100644 index 000000000..4d95c4bd7 --- /dev/null +++ b/apps/api/src/app/benchmark/benchmark.module.ts @@ -0,0 +1,24 @@ +import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; +import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module'; +import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; +import { MarketDataModule } from '@ghostfolio/api/services/market-data.module'; +import { PropertyModule } from '@ghostfolio/api/services/property/property.module'; +import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile.module'; +import { Module } from '@nestjs/common'; + +import { BenchmarkController } from './benchmark.controller'; +import { BenchmarkService } from './benchmark.service'; + +@Module({ + controllers: [BenchmarkController], + imports: [ + ConfigurationModule, + DataProviderModule, + MarketDataModule, + PropertyModule, + RedisCacheModule, + SymbolProfileModule + ], + providers: [BenchmarkService] +}) +export class BenchmarkModule {} diff --git a/apps/api/src/app/benchmark/benchmark.service.ts b/apps/api/src/app/benchmark/benchmark.service.ts new file mode 100644 index 000000000..5780c768a --- /dev/null +++ b/apps/api/src/app/benchmark/benchmark.service.ts @@ -0,0 +1,77 @@ +import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; +import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; +import { MarketDataService } from '@ghostfolio/api/services/market-data.service'; +import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service'; +import { BenchmarkResponse, UniqueAsset } from '@ghostfolio/common/interfaces'; +import { Injectable } from '@nestjs/common'; +import Big from 'big.js'; + +@Injectable() +export class BenchmarkService { + private readonly CACHE_KEY_BENCHMARKS = 'BENCHMARKS'; + + public constructor( + private readonly dataProviderService: DataProviderService, + private readonly marketDataService: MarketDataService, + private readonly redisCacheService: RedisCacheService, + private readonly symbolProfileService: SymbolProfileService + ) {} + + public async getBenchmarks( + benchmarkAssets: UniqueAsset[] + ): Promise { + let benchmarks: BenchmarkResponse['benchmarks']; + + try { + benchmarks = JSON.parse( + await this.redisCacheService.get(this.CACHE_KEY_BENCHMARKS) + ); + + if (benchmarks) { + return benchmarks; + } + } catch {} + + const promises: Promise[] = []; + + const [quotes, assetProfiles] = await Promise.all([ + this.dataProviderService.getQuotes(benchmarkAssets), + this.symbolProfileService.getSymbolProfiles(benchmarkAssets) + ]); + + for (const benchmarkAsset of benchmarkAssets) { + promises.push(this.marketDataService.getMax(benchmarkAsset)); + } + + const allTimeHighs = await Promise.all(promises); + + benchmarks = allTimeHighs.map((allTimeHigh, index) => { + const { marketPrice } = quotes[benchmarkAssets[index].symbol]; + + const performancePercentFromAllTimeHigh = new Big(marketPrice) + .div(allTimeHigh) + .minus(1); + + return { + name: assetProfiles.find(({ dataSource, symbol }) => { + return ( + dataSource === benchmarkAssets[index].dataSource && + symbol === benchmarkAssets[index].symbol + ); + })?.name, + performances: { + allTimeHigh: { + performancePercent: performancePercentFromAllTimeHigh.toNumber() + } + } + }; + }); + + await this.redisCacheService.set( + this.CACHE_KEY_BENCHMARKS, + JSON.stringify(benchmarks) + ); + + return benchmarks; + } +} diff --git a/apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts b/apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts index f400923e8..4d13a1ae3 100644 --- a/apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts +++ b/apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts @@ -1,5 +1,7 @@ -import { EnhancedSymbolProfile } from '@ghostfolio/api/services/interfaces/symbol-profile.interface'; -import { HistoricalDataItem } from '@ghostfolio/common/interfaces'; +import { + EnhancedSymbolProfile, + HistoricalDataItem +} from '@ghostfolio/common/interfaces'; import { OrderWithAccount } from '@ghostfolio/common/types'; import { Tag } from '@prisma/client'; diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 1ac6bb349..da08f8e52 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -19,7 +19,6 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration.ser import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service'; -import { EnhancedSymbolProfile } from '@ghostfolio/api/services/interfaces/symbol-profile.interface'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service'; import { ASSET_SUB_CLASS_EMERGENCY_FUND, @@ -28,6 +27,7 @@ import { import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper'; import { Accounts, + EnhancedSymbolProfile, Filter, HistoricalDataItem, PortfolioDetails, @@ -375,7 +375,7 @@ export class PortfolioService { const [dataProviderResponses, symbolProfiles] = await Promise.all([ this.dataProviderService.getQuotes(dataGatheringItems), - this.symbolProfileService.getSymbolProfiles(symbols) + this.symbolProfileService.getSymbolProfilesBySymbols(symbols) ]); const symbolProfileMap: { [symbol: string]: EnhancedSymbolProfile } = {}; @@ -518,9 +518,8 @@ export class PortfolioService { } const positionCurrency = orders[0].SymbolProfile.currency; - const [SymbolProfile] = await this.symbolProfileService.getSymbolProfiles([ - aSymbol - ]); + const [SymbolProfile] = + await this.symbolProfileService.getSymbolProfilesBySymbols([aSymbol]); const portfolioOrders: PortfolioOrder[] = orders .filter((order) => { @@ -768,7 +767,7 @@ export class PortfolioService { const [dataProviderResponses, symbolProfiles] = await Promise.all([ this.dataProviderService.getQuotes(dataGatheringItem), - this.symbolProfileService.getSymbolProfiles(symbols) + this.symbolProfileService.getSymbolProfilesBySymbols(symbols) ]); const symbolProfileMap: { [symbol: string]: EnhancedSymbolProfile } = {}; diff --git a/apps/api/src/app/symbol/interfaces/symbol-item.interface.ts b/apps/api/src/app/symbol/interfaces/symbol-item.interface.ts index 51ed38d4d..358658672 100644 --- a/apps/api/src/app/symbol/interfaces/symbol-item.interface.ts +++ b/apps/api/src/app/symbol/interfaces/symbol-item.interface.ts @@ -1,9 +1,7 @@ -import { HistoricalDataItem } from '@ghostfolio/common/interfaces'; -import { DataSource } from '@prisma/client'; +import { HistoricalDataItem, UniqueAsset } from '@ghostfolio/common/interfaces'; -export interface SymbolItem { +export interface SymbolItem extends UniqueAsset { currency: string; - dataSource: DataSource; historicalData: HistoricalDataItem[]; marketPrice: number; } diff --git a/apps/api/src/app/symbol/symbol.service.ts b/apps/api/src/app/symbol/symbol.service.ts index 6cfcbc209..e24aa71b2 100644 --- a/apps/api/src/app/symbol/symbol.service.ts +++ b/apps/api/src/app/symbol/symbol.service.ts @@ -55,7 +55,8 @@ export class SymbolService { currency, historicalData, marketPrice, - dataSource: dataGatheringItem.dataSource + dataSource: dataGatheringItem.dataSource, + symbol: dataGatheringItem.symbol }; } diff --git a/apps/api/src/services/data-gathering.service.ts b/apps/api/src/services/data-gathering.service.ts index 61adaa19e..507e1e146 100644 --- a/apps/api/src/services/data-gathering.service.ts +++ b/apps/api/src/services/data-gathering.service.ts @@ -247,11 +247,12 @@ export class DataGatheringService { const assetProfiles = await this.dataProviderService.getAssetProfiles( uniqueAssets ); - const symbolProfiles = await this.symbolProfileService.getSymbolProfiles( - uniqueAssets.map(({ symbol }) => { - return symbol; - }) - ); + const symbolProfiles = + await this.symbolProfileService.getSymbolProfilesBySymbols( + uniqueAssets.map(({ symbol }) => { + return symbol; + }) + ); for (const [symbol, assetProfile] of Object.entries(assetProfiles)) { const symbolMapping = symbolProfiles.find((symbolProfile) => { diff --git a/apps/api/src/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service.ts b/apps/api/src/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service.ts index f0ee84237..26437bcdf 100644 --- a/apps/api/src/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service.ts +++ b/apps/api/src/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service.ts @@ -46,9 +46,8 @@ export class GhostfolioScraperApiService implements DataProviderInterface { try { const symbol = aSymbol; - const [symbolProfile] = await this.symbolProfileService.getSymbolProfiles( - [symbol] - ); + const [symbolProfile] = + await this.symbolProfileService.getSymbolProfilesBySymbols([symbol]); const { defaultMarketPrice, selector, url } = symbolProfile.scraperConfiguration; @@ -108,9 +107,8 @@ export class GhostfolioScraperApiService implements DataProviderInterface { } try { - const symbolProfiles = await this.symbolProfileService.getSymbolProfiles( - aSymbols - ); + const symbolProfiles = + await this.symbolProfileService.getSymbolProfilesBySymbols(aSymbols); const marketData = await this.prismaService.marketData.findMany({ distinct: ['symbol'], diff --git a/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts b/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts index 97022706f..b196df532 100644 --- a/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts +++ b/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts @@ -91,9 +91,8 @@ export class GoogleSheetsService implements DataProviderInterface { try { const response: { [symbol: string]: IDataProviderResponse } = {}; - const symbolProfiles = await this.symbolProfileService.getSymbolProfiles( - aSymbols - ); + const symbolProfiles = + await this.symbolProfileService.getSymbolProfilesBySymbols(aSymbols); const sheet = await this.getSheet({ sheetId: this.configurationService.get('GOOGLE_SHEETS_ID'), diff --git a/apps/api/src/services/market-data.service.ts b/apps/api/src/services/market-data.service.ts index 0afb5a811..9dd3e4773 100644 --- a/apps/api/src/services/market-data.service.ts +++ b/apps/api/src/services/market-data.service.ts @@ -34,6 +34,20 @@ export class MarketDataService { }); } + public async getMax({ dataSource, symbol }: UniqueAsset): Promise { + const aggregations = await this.prismaService.marketData.aggregate({ + _max: { + marketPrice: true + }, + where: { + dataSource, + symbol + } + }); + + return aggregations._max.marketPrice; + } + public async getRange({ dateQuery, symbols diff --git a/apps/api/src/services/symbol-profile.service.ts b/apps/api/src/services/symbol-profile.service.ts index d7a0c4cfd..c91da6d61 100644 --- a/apps/api/src/services/symbol-profile.service.ts +++ b/apps/api/src/services/symbol-profile.service.ts @@ -1,6 +1,10 @@ -import { EnhancedSymbolProfile } from '@ghostfolio/api/services/interfaces/symbol-profile.interface'; import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { UNKNOWN_KEY } from '@ghostfolio/common/config'; +import { + EnhancedSymbolProfile, + ScraperConfiguration, + UniqueAsset +} from '@ghostfolio/common/interfaces'; import { Country } from '@ghostfolio/common/interfaces/country.interface'; import { Sector } from '@ghostfolio/common/interfaces/sector.interface'; import { Injectable } from '@nestjs/common'; @@ -12,8 +16,6 @@ import { } from '@prisma/client'; import { continents, countries } from 'countries-list'; -import { ScraperConfiguration } from './data-provider/ghostfolio-scraper-api/interfaces/scraper-configuration.interface'; - @Injectable() export class SymbolProfileService { public constructor(private readonly prismaService: PrismaService) {} @@ -37,6 +39,35 @@ export class SymbolProfileService { } public async getSymbolProfiles( + aUniqueAssets: UniqueAsset[] + ): Promise { + return this.prismaService.symbolProfile + .findMany({ + include: { SymbolProfileOverrides: true }, + where: { + AND: [ + { + dataSource: { + in: aUniqueAssets.map(({ dataSource }) => { + return dataSource; + }) + }, + symbol: { + in: aUniqueAssets.map(({ symbol }) => { + return symbol; + }) + } + } + ] + } + }) + .then((symbolProfiles) => this.getSymbols(symbolProfiles)); + } + + /** + * @deprecated + */ + public async getSymbolProfilesBySymbols( symbols: string[] ): Promise { return this.prismaService.symbolProfile diff --git a/apps/client/src/app/components/home-market/home-market.component.ts b/apps/client/src/app/components/home-market/home-market.component.ts index 0ea2f5944..9500c6e2d 100644 --- a/apps/client/src/app/components/home-market/home-market.component.ts +++ b/apps/client/src/app/components/home-market/home-market.component.ts @@ -4,6 +4,7 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; import { ghostfolioFearAndGreedIndexSymbol } from '@ghostfolio/common/config'; import { resetHours } from '@ghostfolio/common/helper'; import { + Benchmark, HistoricalDataItem, InfoItem, User @@ -18,6 +19,7 @@ import { takeUntil } from 'rxjs/operators'; templateUrl: './home-market.html' }) export class HomeMarketComponent implements OnDestroy, OnInit { + public benchmarks: Benchmark[]; public fearAndGreedIndex: number; public hasPermissionToAccessFearAndGreedIndex: boolean; public historicalData: HistoricalDataItem[]; @@ -73,6 +75,15 @@ export class HomeMarketComponent implements OnDestroy, OnInit { }); } + this.dataService + .fetchBenchmarks() + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(({ benchmarks }) => { + this.benchmarks = benchmarks; + + this.changeDetectorRef.markForCheck(); + }); + this.changeDetectorRef.markForCheck(); } }); diff --git a/apps/client/src/app/components/home-market/home-market.html b/apps/client/src/app/components/home-market/home-market.html index f3d8315dd..b55103bf0 100644 --- a/apps/client/src/app/components/home-market/home-market.html +++ b/apps/client/src/app/components/home-market/home-market.html @@ -1,13 +1,12 @@ -
-
+
+

Markets

+
Last {{ numberOfDays }} Days
+ +
+
+ + +
+
diff --git a/apps/client/src/app/components/home-market/home-market.module.ts b/apps/client/src/app/components/home-market/home-market.module.ts index 01267b426..2c831a221 100644 --- a/apps/client/src/app/components/home-market/home-market.module.ts +++ b/apps/client/src/app/components/home-market/home-market.module.ts @@ -1,6 +1,7 @@ import { CommonModule } from '@angular/common'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { GfFearAndGreedIndexModule } from '@ghostfolio/client/components/fear-and-greed-index/fear-and-greed-index.module'; +import { GfBenchmarkModule } from '@ghostfolio/ui/benchmark/benchmark.module'; import { GfLineChartModule } from '@ghostfolio/ui/line-chart/line-chart.module'; import { HomeMarketComponent } from './home-market.component'; @@ -8,7 +9,12 @@ import { HomeMarketComponent } from './home-market.component'; @NgModule({ declarations: [HomeMarketComponent], exports: [], - imports: [CommonModule, GfFearAndGreedIndexModule, GfLineChartModule], + imports: [ + CommonModule, + GfBenchmarkModule, + GfFearAndGreedIndexModule, + GfLineChartModule + ], providers: [], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) diff --git a/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts b/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts index 05caca115..3e9006111 100644 --- a/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts +++ b/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts @@ -7,9 +7,9 @@ import { OnInit } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { EnhancedSymbolProfile } from '@ghostfolio/api/services/interfaces/symbol-profile.interface'; import { DataService } from '@ghostfolio/client/services/data.service'; import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper'; +import { EnhancedSymbolProfile } from '@ghostfolio/common/interfaces'; import { OrderWithAccount } from '@ghostfolio/common/types'; import { LineChartItem } from '@ghostfolio/ui/line-chart/interfaces/line-chart.interface'; import { Tag } from '@prisma/client'; diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 5c946d036..c0dd5ef89 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -19,6 +19,7 @@ import { Accounts, AdminData, AdminMarketData, + BenchmarkResponse, Export, Filter, InfoItem, @@ -90,6 +91,10 @@ export class DataService { return this.http.get('/api/v1/access'); } + public fetchBenchmarks() { + return this.http.get('/api/v1/benchmark'); + } + public fetchChart({ range }: { range: DateRange }) { return this.http.get('/api/v1/portfolio/chart', { params: { range } diff --git a/apps/client/src/styles.scss b/apps/client/src/styles.scss index ed670f1d4..8638c00ce 100644 --- a/apps/client/src/styles.scss +++ b/apps/client/src/styles.scss @@ -60,12 +60,8 @@ body { } ngx-skeleton-loader { - line-height: 0; - outline: 0; - .loader { background-color: #323232; - outline: 0; } } @@ -117,9 +113,13 @@ ion-icon { ngx-skeleton-loader { display: block; + line-height: 0; + outline: 0; .loader { + display: flex; margin: 0 !important; + outline: 0; } } diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 7fa9e9d00..410d0498f 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -48,6 +48,7 @@ export const DEFAULT_DATE_FORMAT_MONTH_YEAR = 'MMM yyyy'; export const GATHER_ASSET_PROFILE_PROCESS = 'GATHER_ASSET_PROFILE'; +export const PROPERTY_BENCHMARKS = 'BENCHMARKS'; export const PROPERTY_COUPONS = 'COUPONS'; export const PROPERTY_CURRENCIES = 'CURRENCIES'; export const PROPERTY_IS_READ_ONLY_MODE = 'IS_READ_ONLY_MODE'; diff --git a/libs/common/src/lib/interfaces/benchmark.interface.ts b/libs/common/src/lib/interfaces/benchmark.interface.ts new file mode 100644 index 000000000..146fc4b07 --- /dev/null +++ b/libs/common/src/lib/interfaces/benchmark.interface.ts @@ -0,0 +1,10 @@ +import { EnhancedSymbolProfile } from './enhanced-symbol-profile.interface'; + +export interface Benchmark { + name: EnhancedSymbolProfile['name']; + performances: { + allTimeHigh: { + performancePercent: number; + }; + }; +} diff --git a/apps/api/src/services/interfaces/symbol-profile.interface.ts b/libs/common/src/lib/interfaces/enhanced-symbol-profile.interface.ts similarity index 61% rename from apps/api/src/services/interfaces/symbol-profile.interface.ts rename to libs/common/src/lib/interfaces/enhanced-symbol-profile.interface.ts index e8c00ecd6..657c9acd6 100644 --- a/apps/api/src/services/interfaces/symbol-profile.interface.ts +++ b/libs/common/src/lib/interfaces/enhanced-symbol-profile.interface.ts @@ -1,8 +1,9 @@ -import { ScraperConfiguration } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/interfaces/scraper-configuration.interface'; -import { Country } from '@ghostfolio/common/interfaces/country.interface'; -import { Sector } from '@ghostfolio/common/interfaces/sector.interface'; import { AssetClass, AssetSubClass, DataSource } from '@prisma/client'; +import { Country } from './country.interface'; +import { ScraperConfiguration } from './scraper-configuration.interface'; +import { Sector } from './sector.interface'; + export interface EnhancedSymbolProfile { assetClass: AssetClass; assetSubClass: AssetSubClass; diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index 96275e4d3..cb31e246e 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -6,7 +6,9 @@ import { AdminMarketData, AdminMarketDataItem } from './admin-market-data.interface'; +import { Benchmark } from './benchmark.interface'; import { Coupon } from './coupon.interface'; +import { EnhancedSymbolProfile } from './enhanced-symbol-profile.interface'; import { Export } from './export.interface'; import { FilterGroup } from './filter-group.interface'; import { Filter } from './filter.interface'; @@ -24,8 +26,10 @@ import { PortfolioReportRule } from './portfolio-report-rule.interface'; import { PortfolioReport } from './portfolio-report.interface'; import { PortfolioSummary } from './portfolio-summary.interface'; import { Position } from './position.interface'; +import { BenchmarkResponse } from './responses/benchmark-response.interface'; import { ResponseError } from './responses/errors.interface'; import { PortfolioPerformanceResponse } from './responses/portfolio-performance-response.interface'; +import { ScraperConfiguration } from './scraper-configuration.interface'; import { TimelinePosition } from './timeline-position.interface'; import { UniqueAsset } from './unique-asset.interface'; import { UserSettings } from './user-settings.interface'; @@ -39,7 +43,10 @@ export { AdminMarketData, AdminMarketDataDetails, AdminMarketDataItem, + Benchmark, + BenchmarkResponse, Coupon, + EnhancedSymbolProfile, Export, Filter, FilterGroup, @@ -59,6 +66,7 @@ export { PortfolioSummary, Position, ResponseError, + ScraperConfiguration, TimelinePosition, UniqueAsset, User, diff --git a/libs/common/src/lib/interfaces/responses/benchmark-response.interface.ts b/libs/common/src/lib/interfaces/responses/benchmark-response.interface.ts new file mode 100644 index 000000000..262d55fba --- /dev/null +++ b/libs/common/src/lib/interfaces/responses/benchmark-response.interface.ts @@ -0,0 +1,5 @@ +import { Benchmark } from '../benchmark.interface'; + +export interface BenchmarkResponse { + benchmarks: Benchmark[]; +} diff --git a/apps/api/src/services/data-provider/ghostfolio-scraper-api/interfaces/scraper-configuration.interface.ts b/libs/common/src/lib/interfaces/scraper-configuration.interface.ts similarity index 100% rename from apps/api/src/services/data-provider/ghostfolio-scraper-api/interfaces/scraper-configuration.interface.ts rename to libs/common/src/lib/interfaces/scraper-configuration.interface.ts diff --git a/libs/ui/src/lib/benchmark/benchmark.component.html b/libs/ui/src/lib/benchmark/benchmark.component.html new file mode 100644 index 000000000..5bba27f51 --- /dev/null +++ b/libs/ui/src/lib/benchmark/benchmark.component.html @@ -0,0 +1,32 @@ +
+
+ {{ benchmark.name }} +
+
+ +
+ +
+ from All Time Highfrom ATH +
+
diff --git a/libs/ui/src/lib/benchmark/benchmark.component.scss b/libs/ui/src/lib/benchmark/benchmark.component.scss new file mode 100644 index 000000000..5d4e87f30 --- /dev/null +++ b/libs/ui/src/lib/benchmark/benchmark.component.scss @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/libs/ui/src/lib/benchmark/benchmark.component.ts b/libs/ui/src/lib/benchmark/benchmark.component.ts new file mode 100644 index 000000000..a5f439364 --- /dev/null +++ b/libs/ui/src/lib/benchmark/benchmark.component.ts @@ -0,0 +1,15 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { Benchmark } from '@ghostfolio/common/interfaces'; + +@Component({ + selector: 'gf-benchmark', + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './benchmark.component.html', + styleUrls: ['./benchmark.component.scss'] +}) +export class BenchmarkComponent { + @Input() benchmark: Benchmark; + @Input() locale: string; + + public constructor() {} +} diff --git a/libs/ui/src/lib/benchmark/benchmark.module.ts b/libs/ui/src/lib/benchmark/benchmark.module.ts new file mode 100644 index 000000000..3a0eeb5dd --- /dev/null +++ b/libs/ui/src/lib/benchmark/benchmark.module.ts @@ -0,0 +1,14 @@ +import { CommonModule } from '@angular/common'; +import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; +import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; + +import { GfValueModule } from '../value'; +import { BenchmarkComponent } from './benchmark.component'; + +@NgModule({ + declarations: [BenchmarkComponent], + exports: [BenchmarkComponent], + imports: [CommonModule, GfValueModule, NgxSkeletonLoaderModule], + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class GfBenchmarkModule {} diff --git a/libs/ui/src/lib/benchmark/index.ts b/libs/ui/src/lib/benchmark/index.ts new file mode 100644 index 000000000..b8cd0c1a8 --- /dev/null +++ b/libs/ui/src/lib/benchmark/index.ts @@ -0,0 +1 @@ +export * from './benchmark.module'; diff --git a/libs/ui/src/lib/value/value.component.html b/libs/ui/src/lib/value/value.component.html index 8ed9c9bdf..b55b16d44 100644 --- a/libs/ui/src/lib/value/value.component.html +++ b/libs/ui/src/lib/value/value.component.html @@ -58,7 +58,7 @@ *ngIf="value === undefined" animation="pulse" [theme]="{ - height: size === 'large' ? '2.5rem' : '1.5rem', + height: size === 'large' ? '2.5rem' : size === 'medium' ? '2rem' : '1.5rem', width: '5rem' }" >