diff --git a/CHANGELOG.md b/CHANGELOG.md index 642c64392..1378bd215 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 data gathering status column to the historical market data table of the admin control +- Deactivated asset profiles automatically on delisting in the _Yahoo Finance_ service ### Changed diff --git a/apps/api/src/services/data-provider/errors/asset-profile-delisted.error.ts b/apps/api/src/services/data-provider/errors/asset-profile-delisted.error.ts new file mode 100644 index 000000000..d326ee74b --- /dev/null +++ b/apps/api/src/services/data-provider/errors/asset-profile-delisted.error.ts @@ -0,0 +1,6 @@ +export class AssetProfileDelistedError extends Error { + public constructor(message: string) { + super(message); + this.name = 'AssetProfileDelistedError'; + } +} 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 72ae1ff97..d8f4a143f 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,5 +1,6 @@ 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'; import { DataProviderInterface, GetAssetProfileParams, @@ -143,12 +144,16 @@ export class YahooFinanceService implements DataProviderInterface { return response; } catch (error) { - throw new Error( - `Could not get historical market data for ${symbol} (${this.getName()}) from ${format( - from, - DATE_FORMAT - )} to ${format(to, DATE_FORMAT)}: [${error.name}] ${error.message}` - ); + if (error.message === 'No data found, symbol may be delisted') { + throw new AssetProfileDelistedError(error.message); + } else { + throw new Error( + `Could not get historical market data for ${symbol} (${this.getName()}) from ${format( + from, + DATE_FORMAT + )} to ${format(to, DATE_FORMAT)}: [${error.name}] ${error.message}` + ); + } } } 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 eedad7475..d7fcc5ab3 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 @@ -1,6 +1,8 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; +import { AssetProfileDelistedError } from '@ghostfolio/api/services/data-provider/errors/asset-profile-delisted.error'; import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; +import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { DATA_GATHERING_QUEUE, DEFAULT_PROCESSOR_GATHER_ASSET_PROFILE_CONCURRENCY, @@ -33,7 +35,8 @@ export class DataGatheringProcessor { public constructor( private readonly dataGatheringService: DataGatheringService, private readonly dataProviderService: DataProviderService, - private readonly marketDataService: MarketDataService + private readonly marketDataService: MarketDataService, + private readonly symbolProfileService: SymbolProfileService ) {} @Process({ @@ -76,8 +79,9 @@ export class DataGatheringProcessor { name: GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_NAME }) public async gatherHistoricalMarketData(job: Job) { + const { dataSource, date, symbol } = job.data; + try { - const { dataSource, date, symbol } = job.data; let currentDate = parseISO(date as unknown as string); Logger.log( @@ -142,12 +146,26 @@ export class DataGatheringProcessor { `DataGatheringProcessor (${GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_NAME})` ); } catch (error) { + if (error instanceof AssetProfileDelistedError) { + await this.symbolProfileService.updateSymbolProfile( + { + dataSource, + symbol + }, + { + isActive: false + } + ); + + await job.discard(); + } + Logger.error( error, `DataGatheringProcessor (${GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_NAME})` ); - throw new Error(error); + throw error; } } }