diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d9d70dd9..d3d942f24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed the benchmark label in the tooltip of the benchmark comparator on the analysis page - Fixed the _Storybook_ setup by loading the `@angular/localize` polyfill centrally - Fixed an issue in the activities import where an unused custom asset profile was created if the related activities were not imported +- Fixed the missing close price in the historical market data of the _Yahoo Finance_ service by falling back to the market price of the quote +- Fixed the historical market data gathering by no longer storing the last known market price for the most recent dates without data from the data provider ## 3.57.0 - 2026-08-21 diff --git a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts index 9b0366168..d0c7a204a 100644 --- a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts +++ b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts @@ -143,30 +143,39 @@ export class YahooFinanceService implements DataProviderInterface { [date: string]: DataProviderHistoricalResponse; }> { try { - const historicalResult = this.convertToHistoricalResult( - await this.yahooFinance.chart( - this.yahooFinanceDataEnhancerService.convertToYahooFinanceSymbol( - symbol - ), - { - interval: '1d', - period1: format(from, DATE_FORMAT), - period2: format( - isSameDay(from, to) ? addDays(to, 1) : to, - DATE_FORMAT - ) - } - ) + const chartResult = await this.yahooFinance.chart( + this.yahooFinanceDataEnhancerService.convertToYahooFinanceSymbol( + symbol + ), + { + interval: '1d', + period1: format(from, DATE_FORMAT), + period2: format( + isSameDay(from, to) ? addDays(to, 1) : to, + DATE_FORMAT + ) + } ); + const historicalResult = this.convertToHistoricalResult(chartResult); + const response: { [date: string]: DataProviderHistoricalResponse; } = {}; - for (const historicalItem of historicalResult) { - response[format(historicalItem.date, DATE_FORMAT)] = { - marketPrice: historicalItem.close - }; + for (const { close, date } of historicalResult) { + // The chart endpoint can omit the close price of a historical item, for + // example if the daily data of an exchange has not been consolidated yet. + // In this case, the market price of the corresponding quote is taken. + const marketPrice = + close ?? + (isSameDay(date, chartResult.meta.regularMarketTime) + ? chartResult.meta.regularMarketPrice + : undefined); + + if (marketPrice) { + response[format(date, DATE_FORMAT)] = { marketPrice }; + } } return response; diff --git a/apps/api/src/services/queues/data-gathering/data-gathering.processor.ts b/apps/api/src/services/queues/data-gathering/data-gathering.processor.ts index 008603fa8..b49b30cc8 100644 --- a/apps/api/src/services/queues/data-gathering/data-gathering.processor.ts +++ b/apps/api/src/services/queues/data-gathering/data-gathering.processor.ts @@ -125,6 +125,7 @@ export class DataGatheringProcessor { const data: Prisma.MarketDataUpdateInput[] = []; let lastMarketPrice: number; + let numberOfMarketDataItemsToKeep = 0; while ( isBefore( @@ -139,15 +140,13 @@ export class DataGatheringProcessor { ) ) ) { - if ( + const marketPriceOfDataProvider = historicalData[assetProfileIdentifier]?.[ format(currentDate, DATE_FORMAT) - ]?.marketPrice - ) { - lastMarketPrice = - historicalData[assetProfileIdentifier]?.[ - format(currentDate, DATE_FORMAT) - ]?.marketPrice; + ]?.marketPrice; + + if (marketPriceOfDataProvider) { + lastMarketPrice = marketPriceOfDataProvider; } if (lastMarketPrice) { @@ -158,11 +157,21 @@ export class DataGatheringProcessor { marketPrice: lastMarketPrice, state: 'CLOSE' }); + + if (marketPriceOfDataProvider) { + numberOfMarketDataItemsToKeep = data.length; + } } currentDate = addDays(currentDate, 1); } + // A gap at the end means that the market data is not available yet, in + // contrast to a gap in between, which means that the market was closed. + // Therefore, the market prices which are carried forward after the last + // market price of the data provider are discarded. + data.splice(numberOfMarketDataItemsToKeep); + if (force) { await this.marketDataService.replaceForSymbol({ data, diff --git a/apps/api/src/services/queues/data-gathering/data-gathering.service.ts b/apps/api/src/services/queues/data-gathering/data-gathering.service.ts index 968acead5..9596b7614 100644 --- a/apps/api/src/services/queues/data-gathering/data-gathering.service.ts +++ b/apps/api/src/services/queues/data-gathering/data-gathering.service.ts @@ -310,9 +310,9 @@ export class DataGatheringService { }); const marketPrice = - historicalData[getAssetProfileIdentifier({ dataSource, symbol })][ + historicalData[getAssetProfileIdentifier({ dataSource, symbol })]?.[ format(date, DATE_FORMAT) - ].marketPrice; + ]?.marketPrice; if (marketPrice) { return await this.prismaService.marketData.upsert({