diff --git a/CHANGELOG.md b/CHANGELOG.md index 92014bdd9..1e7713c91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added the search functionality for the `EOD_HISTORICAL_DATA` data source type + ### Changed - Improved the usability of the _FIRE_ calculator diff --git a/apps/api/src/services/data-provider/data-provider.service.ts b/apps/api/src/services/data-provider/data-provider.service.ts index 0173dc82e..980b1a620 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -272,14 +272,20 @@ export class DataProviderService { const searchResults = await Promise.all(promises); - searchResults.forEach((searchResult) => { - lookupItems = lookupItems.concat(searchResult.items); + searchResults.forEach(({ items }) => { + if (items?.length > 0) { + lookupItems = lookupItems.concat(items); + } }); - const filteredItems = lookupItems.filter((lookupItem) => { - // Only allow symbols with supported currency - return lookupItem.currency ? true : false; - }); + const filteredItems = lookupItems + .filter((lookupItem) => { + // Only allow symbols with supported currency + return lookupItem.currency ? true : false; + }) + .sort(({ name: name1 }, { name: name2 }) => { + return name1?.toLowerCase().localeCompare(name2?.toLowerCase()); + }); return { items: filteredItems diff --git a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts index cbfd67e2e..447268ad0 100644 --- a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts +++ b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts @@ -5,13 +5,12 @@ import { IDataProviderHistoricalResponse, IDataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; -import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { Granularity } from '@ghostfolio/common/types'; import { Injectable, Logger } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; import bent from 'bent'; -import { format } from 'date-fns'; +import { format, isToday } from 'date-fns'; @Injectable() export class EodHistoricalDataService implements DataProviderInterface { @@ -19,8 +18,7 @@ export class EodHistoricalDataService implements DataProviderInterface { private readonly URL = 'https://eodhistoricaldata.com/api'; public constructor( - private readonly configurationService: ConfigurationService, - private readonly symbolProfileService: SymbolProfileService + private readonly configurationService: ConfigurationService ) { this.apiKey = this.configurationService.get('EOD_HISTORICAL_DATA_API_KEY'); } @@ -32,8 +30,12 @@ export class EodHistoricalDataService implements DataProviderInterface { public async getAssetProfile( aSymbol: string ): Promise> { + const { items } = await this.search(aSymbol); + return { - dataSource: this.getName() + currency: items[0]?.currency, + dataSource: this.getName(), + name: items[0]?.name }; } @@ -122,32 +124,30 @@ export class EodHistoricalDataService implements DataProviderInterface { 200 ); - const [response, symbolProfiles] = await Promise.all([ + const [realTimeResponse, searchResponse] = await Promise.all([ get(), - this.symbolProfileService.getSymbolProfiles( - aSymbols.map((symbol) => { - return { - symbol, - dataSource: DataSource.EOD_HISTORICAL_DATA - }; - }) - ) + this.search(aSymbols[0]) ]); - const quotes = aSymbols.length === 1 ? [response] : response; - - return quotes.reduce((result, item, index, array) => { - result[item.code] = { - currency: symbolProfiles.find((symbolProfile) => { - return symbolProfile.symbol === item.code; - })?.currency, - dataSource: DataSource.EOD_HISTORICAL_DATA, - marketPrice: item.close, - marketState: 'delayed' - }; + const quotes = + aSymbols.length === 1 ? [realTimeResponse] : realTimeResponse; + + return quotes.reduce( + ( + result: { [symbol: string]: IDataProviderResponse }, + { close, code, timestamp } + ) => { + result[code] = { + currency: searchResponse?.items[0]?.currency, + dataSource: DataSource.EOD_HISTORICAL_DATA, + marketPrice: close, + marketState: isToday(new Date(timestamp * 1000)) ? 'open' : 'closed' + }; - return result; - }, {}); + return result; + }, + {} + ); } catch (error) { Logger.error(error, 'EodHistoricalDataService'); } @@ -156,6 +156,35 @@ export class EodHistoricalDataService implements DataProviderInterface { } public async search(aQuery: string): Promise<{ items: LookupItem[] }> { - return { items: [] }; + let items: LookupItem[] = []; + + if (aQuery.length <= 2) { + return { items }; + } + + try { + const get = bent( + `${this.URL}/search/${aQuery}?api_token=${this.apiKey}`, + 'GET', + 'json', + 200 + ); + const response = await get(); + + items = response.map( + ({ Code, Currency: currency, Exchange, Name: name }) => { + return { + currency, + name, + dataSource: this.getName(), + symbol: `${Code}.${Exchange}` + }; + } + ); + } catch (error) { + Logger.error(error, 'EodHistoricalDataService'); + } + + return { items }; } } diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html index 2291cf818..5e8245656 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -61,7 +61,8 @@ {{ lookupItem.name }}
{{ lookupItem.symbol | gfSymbol }}{{ lookupItem.symbol | gfSymbol }} ยท {{ lookupItem.currency + }}