diff --git a/CHANGELOG.md b/CHANGELOG.md index fbeff96ca..02c75bbc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Optimized the style of the carousel component on mobile for the testimonial section on the landing page - Introduced action menus in the overview of the admin control panel - Harmonized the name column in the historical market data table of the admin control panel +- Refactored the implementation of the data range functionality (`getRange()`) in the market data service ## 2.21.0 - 2023-11-09 diff --git a/apps/api/src/app/portfolio/current-rate.service.mock.ts b/apps/api/src/app/portfolio/current-rate.service.mock.ts index a9dc03acf..6d64f5869 100644 --- a/apps/api/src/app/portfolio/current-rate.service.mock.ts +++ b/apps/api/src/app/portfolio/current-rate.service.mock.ts @@ -61,6 +61,7 @@ export const CurrentRateServiceMock = { for (const dataGatheringItem of dataGatheringItems) { values.push({ date, + dataSource: dataGatheringItem.dataSource, marketPriceInBaseCurrency: mockGetValue( dataGatheringItem.symbol, date @@ -74,6 +75,7 @@ export const CurrentRateServiceMock = { for (const dataGatheringItem of dataGatheringItems) { values.push({ date, + dataSource: dataGatheringItem.dataSource, marketPriceInBaseCurrency: mockGetValue( dataGatheringItem.symbol, date diff --git a/apps/api/src/app/portfolio/current-rate.service.spec.ts b/apps/api/src/app/portfolio/current-rate.service.spec.ts index 88790d2be..44d8bedb8 100644 --- a/apps/api/src/app/portfolio/current-rate.service.spec.ts +++ b/apps/api/src/app/portfolio/current-rate.service.spec.ts @@ -2,6 +2,7 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; +import { UniqueAsset } from '@ghostfolio/common/interfaces'; import { DataSource, MarketData } from '@prisma/client'; import { CurrentRateService } from './current-rate.service'; @@ -25,30 +26,30 @@ jest.mock('@ghostfolio/api/services/market-data/market-data.service', () => { getRange: ({ dateRangeEnd, dateRangeStart, - symbols + uniqueAssets }: { dateRangeEnd: Date; dateRangeStart: Date; - symbols: string[]; + uniqueAssets: UniqueAsset[]; }) => { return Promise.resolve([ { createdAt: dateRangeStart, - dataSource: DataSource.YAHOO, + dataSource: uniqueAssets[0].dataSource, date: dateRangeStart, id: '8fa48fde-f397-4b0d-adbc-fb940e830e6d', marketPrice: 1841.823902, state: 'CLOSE', - symbol: symbols[0] + symbol: uniqueAssets[0].symbol }, { createdAt: dateRangeEnd, - dataSource: DataSource.YAHOO, + dataSource: uniqueAssets[0].dataSource, date: dateRangeEnd, id: '082d6893-df27-4c91-8a5d-092e84315b56', marketPrice: 1847.839966, state: 'CLOSE', - symbol: symbols[0] + symbol: uniqueAssets[0].symbol } ]); } @@ -134,6 +135,7 @@ describe('CurrentRateService', () => { errors: [], values: [ { + dataSource: 'YAHOO', date: undefined, marketPriceInBaseCurrency: 1841.823902, symbol: 'AMZN' diff --git a/apps/api/src/app/portfolio/current-rate.service.ts b/apps/api/src/app/portfolio/current-rate.service.ts index a4ee317bb..718ec6095 100644 --- a/apps/api/src/app/portfolio/current-rate.service.ts +++ b/apps/api/src/app/portfolio/current-rate.service.ts @@ -2,7 +2,11 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { resetHours } from '@ghostfolio/common/helper'; -import { DataProviderInfo, ResponseError } from '@ghostfolio/common/interfaces'; +import { + DataProviderInfo, + ResponseError, + UniqueAsset +} from '@ghostfolio/common/interfaces'; import { Injectable } from '@nestjs/common'; import { isBefore, isToday } from 'date-fns'; import { flatten, isEmpty, uniqBy } from 'lodash'; @@ -52,6 +56,7 @@ export class CurrentRateService { if (dataResultProvider?.[dataGatheringItem.symbol]?.marketPrice) { result.push({ + dataSource: dataGatheringItem.dataSource, date: today, marketPriceInBaseCurrency: this.exchangeRateDataService.toCurrency( @@ -75,27 +80,30 @@ export class CurrentRateService { ); } - const symbols = dataGatheringItems.map((dataGatheringItem) => { - return dataGatheringItem.symbol; - }); + const uniqueAssets: UniqueAsset[] = dataGatheringItems.map( + ({ dataSource, symbol }) => { + return { dataSource, symbol }; + } + ); promises.push( this.marketDataService .getRange({ dateQuery, - symbols + uniqueAssets }) .then((data) => { - return data.map((marketDataItem) => { + return data.map(({ dataSource, date, marketPrice, symbol }) => { return { - date: marketDataItem.date, + dataSource, + date, + symbol, marketPriceInBaseCurrency: this.exchangeRateDataService.toCurrency( - marketDataItem.marketPrice, - currencies[marketDataItem.symbol], + marketPrice, + currencies[symbol], userCurrency - ), - symbol: marketDataItem.symbol + ) }; }); }) @@ -112,7 +120,7 @@ export class CurrentRateService { }; if (!isEmpty(quoteErrors)) { - for (const { symbol } of quoteErrors) { + for (const { dataSource, symbol } of quoteErrors) { try { // If missing quote, fallback to the latest available historical market price let value: GetValueObject = response.values.find((currentValue) => { @@ -121,6 +129,7 @@ export class CurrentRateService { if (!value) { value = { + dataSource, symbol, date: today, marketPriceInBaseCurrency: 0 diff --git a/apps/api/src/app/portfolio/interfaces/get-value-object.interface.ts b/apps/api/src/app/portfolio/interfaces/get-value-object.interface.ts index e67aca619..661170470 100644 --- a/apps/api/src/app/portfolio/interfaces/get-value-object.interface.ts +++ b/apps/api/src/app/portfolio/interfaces/get-value-object.interface.ts @@ -1,5 +1,6 @@ -export interface GetValueObject { +import { UniqueAsset } from '@ghostfolio/common/interfaces'; + +export interface GetValueObject extends UniqueAsset { date: Date; marketPriceInBaseCurrency: number; - symbol: string; } diff --git a/apps/api/src/app/symbol/symbol.service.ts b/apps/api/src/app/symbol/symbol.service.ts index 5eacbb1b0..32285f422 100644 --- a/apps/api/src/app/symbol/symbol.service.ts +++ b/apps/api/src/app/symbol/symbol.service.ts @@ -40,7 +40,12 @@ export class SymbolService { const marketData = await this.marketDataService.getRange({ dateQuery: { gte: subDays(new Date(), days) }, - symbols: [dataGatheringItem.symbol] + uniqueAssets: [ + { + dataSource: dataGatheringItem.dataSource, + symbol: dataGatheringItem.symbol + } + ] }); historicalData = marketData.map(({ date, marketPrice: value }) => { diff --git a/apps/api/src/services/market-data/market-data.service.ts b/apps/api/src/services/market-data/market-data.service.ts index 5760096bf..52c833784 100644 --- a/apps/api/src/services/market-data/market-data.service.ts +++ b/apps/api/src/services/market-data/market-data.service.ts @@ -59,10 +59,10 @@ export class MarketDataService { public async getRange({ dateQuery, - symbols + uniqueAssets }: { dateQuery: DateQuery; - symbols: string[]; + uniqueAssets: UniqueAsset[]; }): Promise { return await this.prismaService.marketData.findMany({ orderBy: [ @@ -74,10 +74,17 @@ export class MarketDataService { } ], where: { - date: dateQuery, - symbol: { - in: symbols - } + OR: uniqueAssets.map(({ dataSource, symbol }) => { + return { + AND: [ + { + dataSource, + symbol, + date: dateQuery + } + ] + }; + }) } }); }