From 8dbab6cfa8dfd0028a93382f42b36d059f156d1c Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 13 Aug 2026 11:20:29 +0200 Subject: [PATCH] Resolve error when fetching dividends for date ranges without events --- .../yahoo-finance/yahoo-finance.service.ts | 49 ++++++++++++++----- 1 file changed, 38 insertions(+), 11 deletions(-) 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 25d589bbe..b71b5b8cd 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 @@ -1,3 +1,4 @@ +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { CryptocurrencyService } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.service'; import { YahooFinanceDataEnhancerService } from '@ghostfolio/api/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service'; import { AssetProfileDelistedError } from '@ghostfolio/api/services/data-provider/errors/asset-profile-delisted.error'; @@ -21,8 +22,8 @@ import { import { Injectable, Logger } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; -import { addDays, format, isSameDay } from 'date-fns'; -import { getReasonPhrase, StatusCodes } from 'http-status-codes'; +import { addDays, format, isSameDay, isValid } from 'date-fns'; +import { ReasonPhrases, StatusCodes } from 'http-status-codes'; import { uniqBy } from 'lodash'; import YahooFinance from 'yahoo-finance2'; import { ChartResultArray } from 'yahoo-finance2/esm/src/modules/chart'; @@ -45,6 +46,9 @@ export class YahooFinanceService implements DataProviderInterface { private static readonly DELISTED_ERROR_MESSAGE = 'No data found, symbol may be delisted'; + private static readonly RATE_LIMIT_ERROR_MESSAGE = + ReasonPhrases.TOO_MANY_REQUESTS; + private readonly logger = new Logger(YahooFinanceService.name); private readonly yahooFinance = new YahooFinance({ @@ -52,6 +56,7 @@ export class YahooFinanceService implements DataProviderInterface { }); public constructor( + private readonly configurationService: ConfigurationService, private readonly cryptocurrencyService: CryptocurrencyService, private readonly yahooFinanceDataEnhancerService: YahooFinanceDataEnhancerService ) {} @@ -78,9 +83,18 @@ export class YahooFinanceService implements DataProviderInterface { public async getDividends({ from, granularity = 'day', + requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), symbol, to }: GetDividendsParams) { + if (!isValid(from) || !isValid(to)) { + this.logger.error( + `Could not get dividends for ${symbol} (${this.getName()}): Invalid date range` + ); + + return {}; + } + try { const historicalResult = this.convertToDividendResult( await this.yahooFinance.chart( @@ -95,6 +109,9 @@ export class YahooFinanceService implements DataProviderInterface { isSameDay(from, to) ? addDays(to, 1) : to, DATE_FORMAT ) + }, + { + fetchOptions: { signal: AbortSignal.timeout(requestTimeout) } } ) ); @@ -113,16 +130,22 @@ export class YahooFinanceService implements DataProviderInterface { const message = `Could not get dividends for ${symbol} (${this.getName()}) from ${format( from, DATE_FORMAT - )} to ${format(to, DATE_FORMAT)}: [${error?.name}] ${error?.message}`; + )} to ${format(to, DATE_FORMAT)}`; - if ( - error?.code === StatusCodes.TOO_MANY_REQUESTS || - error?.message === YahooFinanceService.DELISTED_ERROR_MESSAGE || - error?.message?.includes(getReasonPhrase(StatusCodes.TOO_MANY_REQUESTS)) + if (error?.message === YahooFinanceService.DELISTED_ERROR_MESSAGE) { + this.logger.warn( + `${message}: ${YahooFinanceService.DELISTED_ERROR_MESSAGE}` + ); + } else if ( + (error?.name === 'HTTPError' && + error?.code === StatusCodes.TOO_MANY_REQUESTS) || + error?.message?.startsWith(YahooFinanceService.RATE_LIMIT_ERROR_MESSAGE) ) { - this.logger.warn(message); + this.logger.warn( + `${message}: ${YahooFinanceService.RATE_LIMIT_ERROR_MESSAGE}` + ); } else { - this.logger.error(message); + this.logger.error(`${message}: [${error?.name}] ${error?.message}`); } return {}; @@ -131,6 +154,7 @@ export class YahooFinanceService implements DataProviderInterface { public async getHistorical({ from, + requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), symbol, to }: GetHistoricalParams): Promise<{ @@ -149,6 +173,9 @@ export class YahooFinanceService implements DataProviderInterface { isSameDay(from, to) ? addDays(to, 1) : to, DATE_FORMAT ) + }, + { + fetchOptions: { signal: AbortSignal.timeout(requestTimeout) } } ) ); @@ -165,7 +192,7 @@ export class YahooFinanceService implements DataProviderInterface { return response; } catch (error) { - if (error.message === YahooFinanceService.DELISTED_ERROR_MESSAGE) { + if (error?.message === YahooFinanceService.DELISTED_ERROR_MESSAGE) { throw new AssetProfileDelistedError( `No data found, ${symbol} (${this.getName()}) may be delisted` ); @@ -174,7 +201,7 @@ export class YahooFinanceService implements DataProviderInterface { `Could not get historical market data for ${symbol} (${this.getName()}) from ${format( from, DATE_FORMAT - )} to ${format(to, DATE_FORMAT)}: [${error.name}] ${error.message}` + )} to ${format(to, DATE_FORMAT)}: [${error?.name}] ${error?.message}` ); } }