Browse Source

Feature/refactor get range in market data service (#2631)

* Refactor to unique asset in getRange()

* Update changelog
pull/2638/head
Thomas Kaul 1 year ago
committed by GitHub
parent
commit
93b6011ddc
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 2
      apps/api/src/app/portfolio/current-rate.service.mock.ts
  3. 14
      apps/api/src/app/portfolio/current-rate.service.spec.ts
  4. 33
      apps/api/src/app/portfolio/current-rate.service.ts
  5. 5
      apps/api/src/app/portfolio/interfaces/get-value-object.interface.ts
  6. 7
      apps/api/src/app/symbol/symbol.service.ts
  7. 19
      apps/api/src/services/market-data/market-data.service.ts

1
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 - 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 - 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 - 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 ## 2.21.0 - 2023-11-09

2
apps/api/src/app/portfolio/current-rate.service.mock.ts

@ -61,6 +61,7 @@ export const CurrentRateServiceMock = {
for (const dataGatheringItem of dataGatheringItems) { for (const dataGatheringItem of dataGatheringItems) {
values.push({ values.push({
date, date,
dataSource: dataGatheringItem.dataSource,
marketPriceInBaseCurrency: mockGetValue( marketPriceInBaseCurrency: mockGetValue(
dataGatheringItem.symbol, dataGatheringItem.symbol,
date date
@ -74,6 +75,7 @@ export const CurrentRateServiceMock = {
for (const dataGatheringItem of dataGatheringItems) { for (const dataGatheringItem of dataGatheringItems) {
values.push({ values.push({
date, date,
dataSource: dataGatheringItem.dataSource,
marketPriceInBaseCurrency: mockGetValue( marketPriceInBaseCurrency: mockGetValue(
dataGatheringItem.symbol, dataGatheringItem.symbol,
date date

14
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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { DataSource, MarketData } from '@prisma/client'; import { DataSource, MarketData } from '@prisma/client';
import { CurrentRateService } from './current-rate.service'; import { CurrentRateService } from './current-rate.service';
@ -25,30 +26,30 @@ jest.mock('@ghostfolio/api/services/market-data/market-data.service', () => {
getRange: ({ getRange: ({
dateRangeEnd, dateRangeEnd,
dateRangeStart, dateRangeStart,
symbols uniqueAssets
}: { }: {
dateRangeEnd: Date; dateRangeEnd: Date;
dateRangeStart: Date; dateRangeStart: Date;
symbols: string[]; uniqueAssets: UniqueAsset[];
}) => { }) => {
return Promise.resolve<MarketData[]>([ return Promise.resolve<MarketData[]>([
{ {
createdAt: dateRangeStart, createdAt: dateRangeStart,
dataSource: DataSource.YAHOO, dataSource: uniqueAssets[0].dataSource,
date: dateRangeStart, date: dateRangeStart,
id: '8fa48fde-f397-4b0d-adbc-fb940e830e6d', id: '8fa48fde-f397-4b0d-adbc-fb940e830e6d',
marketPrice: 1841.823902, marketPrice: 1841.823902,
state: 'CLOSE', state: 'CLOSE',
symbol: symbols[0] symbol: uniqueAssets[0].symbol
}, },
{ {
createdAt: dateRangeEnd, createdAt: dateRangeEnd,
dataSource: DataSource.YAHOO, dataSource: uniqueAssets[0].dataSource,
date: dateRangeEnd, date: dateRangeEnd,
id: '082d6893-df27-4c91-8a5d-092e84315b56', id: '082d6893-df27-4c91-8a5d-092e84315b56',
marketPrice: 1847.839966, marketPrice: 1847.839966,
state: 'CLOSE', state: 'CLOSE',
symbol: symbols[0] symbol: uniqueAssets[0].symbol
} }
]); ]);
} }
@ -134,6 +135,7 @@ describe('CurrentRateService', () => {
errors: [], errors: [],
values: [ values: [
{ {
dataSource: 'YAHOO',
date: undefined, date: undefined,
marketPriceInBaseCurrency: 1841.823902, marketPriceInBaseCurrency: 1841.823902,
symbol: 'AMZN' symbol: 'AMZN'

33
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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { resetHours } from '@ghostfolio/common/helper'; 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 { Injectable } from '@nestjs/common';
import { isBefore, isToday } from 'date-fns'; import { isBefore, isToday } from 'date-fns';
import { flatten, isEmpty, uniqBy } from 'lodash'; import { flatten, isEmpty, uniqBy } from 'lodash';
@ -52,6 +56,7 @@ export class CurrentRateService {
if (dataResultProvider?.[dataGatheringItem.symbol]?.marketPrice) { if (dataResultProvider?.[dataGatheringItem.symbol]?.marketPrice) {
result.push({ result.push({
dataSource: dataGatheringItem.dataSource,
date: today, date: today,
marketPriceInBaseCurrency: marketPriceInBaseCurrency:
this.exchangeRateDataService.toCurrency( this.exchangeRateDataService.toCurrency(
@ -75,27 +80,30 @@ export class CurrentRateService {
); );
} }
const symbols = dataGatheringItems.map((dataGatheringItem) => { const uniqueAssets: UniqueAsset[] = dataGatheringItems.map(
return dataGatheringItem.symbol; ({ dataSource, symbol }) => {
}); return { dataSource, symbol };
}
);
promises.push( promises.push(
this.marketDataService this.marketDataService
.getRange({ .getRange({
dateQuery, dateQuery,
symbols uniqueAssets
}) })
.then((data) => { .then((data) => {
return data.map((marketDataItem) => { return data.map(({ dataSource, date, marketPrice, symbol }) => {
return { return {
date: marketDataItem.date, dataSource,
date,
symbol,
marketPriceInBaseCurrency: marketPriceInBaseCurrency:
this.exchangeRateDataService.toCurrency( this.exchangeRateDataService.toCurrency(
marketDataItem.marketPrice, marketPrice,
currencies[marketDataItem.symbol], currencies[symbol],
userCurrency userCurrency
), )
symbol: marketDataItem.symbol
}; };
}); });
}) })
@ -112,7 +120,7 @@ export class CurrentRateService {
}; };
if (!isEmpty(quoteErrors)) { if (!isEmpty(quoteErrors)) {
for (const { symbol } of quoteErrors) { for (const { dataSource, symbol } of quoteErrors) {
try { try {
// If missing quote, fallback to the latest available historical market price // If missing quote, fallback to the latest available historical market price
let value: GetValueObject = response.values.find((currentValue) => { let value: GetValueObject = response.values.find((currentValue) => {
@ -121,6 +129,7 @@ export class CurrentRateService {
if (!value) { if (!value) {
value = { value = {
dataSource,
symbol, symbol,
date: today, date: today,
marketPriceInBaseCurrency: 0 marketPriceInBaseCurrency: 0

5
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; date: Date;
marketPriceInBaseCurrency: number; marketPriceInBaseCurrency: number;
symbol: string;
} }

7
apps/api/src/app/symbol/symbol.service.ts

@ -40,7 +40,12 @@ export class SymbolService {
const marketData = await this.marketDataService.getRange({ const marketData = await this.marketDataService.getRange({
dateQuery: { gte: subDays(new Date(), days) }, dateQuery: { gte: subDays(new Date(), days) },
symbols: [dataGatheringItem.symbol] uniqueAssets: [
{
dataSource: dataGatheringItem.dataSource,
symbol: dataGatheringItem.symbol
}
]
}); });
historicalData = marketData.map(({ date, marketPrice: value }) => { historicalData = marketData.map(({ date, marketPrice: value }) => {

19
apps/api/src/services/market-data/market-data.service.ts

@ -59,10 +59,10 @@ export class MarketDataService {
public async getRange({ public async getRange({
dateQuery, dateQuery,
symbols uniqueAssets
}: { }: {
dateQuery: DateQuery; dateQuery: DateQuery;
symbols: string[]; uniqueAssets: UniqueAsset[];
}): Promise<MarketData[]> { }): Promise<MarketData[]> {
return await this.prismaService.marketData.findMany({ return await this.prismaService.marketData.findMany({
orderBy: [ orderBy: [
@ -74,10 +74,17 @@ export class MarketDataService {
} }
], ],
where: { where: {
date: dateQuery, OR: uniqueAssets.map(({ dataSource, symbol }) => {
symbol: { return {
in: symbols AND: [
} {
dataSource,
symbol,
date: dateQuery
}
]
};
})
} }
}); });
} }

Loading…
Cancel
Save