From da2c2e2fe4e64263ad81e71fa556e6733632b5b1 Mon Sep 17 00:00:00 2001 From: Arghya Ghosh Date: Sat, 24 Jun 2023 13:48:08 +0530 Subject: [PATCH] Feature/Add ability to search INDEX symbols --- CHANGELOG.md | 1 + apps/api/src/app/symbol/symbol.controller.ts | 4 +++- apps/api/src/app/symbol/symbol.service.ts | 3 +++ .../alpha-vantage/alpha-vantage.service.ts | 10 ++++++++-- .../data-provider/coingecko/coingecko.service.ts | 15 ++++++++------- .../data-provider/data-provider.service.ts | 9 ++++++++- .../eod-historical-data.service.ts | 12 +++++++++--- .../financial-modeling-prep.service.ts | 10 ++++++++-- .../google-sheets/google-sheets.service.ts | 12 +++++++++--- .../interfaces/data-provider.interface.ts | 8 +++++++- .../data-provider/manual/manual.service.ts | 12 +++++++++--- .../data-provider/rapid-api/rapid-api.service.ts | 8 +++++++- .../yahoo-finance/yahoo-finance.service.ts | 16 +++++++++++++--- .../create-asset-profile-dialog.html | 5 ++++- apps/client/src/app/services/data.service.ts | 12 ++++++++++-- .../symbol-autocomplete.component.ts | 6 +++++- 16 files changed, 112 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a2e498a1..04617ca9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the selected item of the holding selector in the import dividends dialog - Extended the symbol search component by asset sub classes +- Add ability to search INDEX symbols ## 1.282.0 - 2023-06-19 diff --git a/apps/api/src/app/symbol/symbol.controller.ts b/apps/api/src/app/symbol/symbol.controller.ts index 810093611..d26ab2106 100644 --- a/apps/api/src/app/symbol/symbol.controller.ts +++ b/apps/api/src/app/symbol/symbol.controller.ts @@ -36,10 +36,12 @@ export class SymbolController { @UseGuards(AuthGuard('jwt')) @UseInterceptors(TransformDataSourceInResponseInterceptor) public async lookupSymbol( - @Query() { query = '' } + @Query('includeIndices') includeIndices: boolean = false, + @Query('query') query = '' ): Promise<{ items: LookupItem[] }> { try { return this.symbolService.lookup({ + includeIndices: includeIndices, query: query.toLowerCase(), user: this.request.user }); diff --git a/apps/api/src/app/symbol/symbol.service.ts b/apps/api/src/app/symbol/symbol.service.ts index 9a08d7ed5..4e7500e70 100644 --- a/apps/api/src/app/symbol/symbol.service.ts +++ b/apps/api/src/app/symbol/symbol.service.ts @@ -81,9 +81,11 @@ export class SymbolService { } public async lookup({ + includeIndices, query, user }: { + includeIndices: boolean; query: string; user: UserWithSettings; }): Promise<{ items: LookupItem[] }> { @@ -95,6 +97,7 @@ export class SymbolService { try { const { items } = await this.dataProviderService.search({ + includeIndices, query, user }); diff --git a/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts b/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts index 2ac2390b3..242e3f6b9 100644 --- a/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts +++ b/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts @@ -114,8 +114,14 @@ export class AlphaVantageService implements DataProviderInterface { return undefined; } - public async search(aQuery: string): Promise<{ items: LookupItem[] }> { - const result = await this.alphaVantage.data.search(aQuery); + public async search({ + includeIndices, + query + }: { + includeIndices?: boolean; + query: string; + }): Promise<{ items: LookupItem[] }> { + const result = await this.alphaVantage.data.search(query); return { items: result?.bestMatches?.map((bestMatch) => { diff --git a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts index 94ec926a6..4738a77ba 100644 --- a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts +++ b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts @@ -164,16 +164,17 @@ export class CoinGeckoService implements DataProviderInterface { return 'bitcoin'; } - public async search(aQuery: string): Promise<{ items: LookupItem[] }> { + public async search({ + includeIndices, + query + }: { + includeIndices?: boolean; + query: string; + }): Promise<{ items: LookupItem[] }> { let items: LookupItem[] = []; try { - const get = bent( - `${this.URL}/search?query=${aQuery}`, - 'GET', - 'json', - 200 - ); + const get = bent(`${this.URL}/search?query=${query}`, 'GET', 'json', 200); const { coins } = await get(); items = coins.map(({ id: symbol, name }) => { 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 6209b89aa..f99a803dc 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -367,9 +367,11 @@ export class DataProviderService { } public async search({ + includeIndices, query, user }: { + includeIndices: boolean; query: string; user: UserWithSettings; }): Promise<{ items: LookupItem[] }> { @@ -392,7 +394,12 @@ export class DataProviderService { } for (const dataSource of dataSources) { - promises.push(this.getDataProvider(DataSource[dataSource]).search(query)); + promises.push( + this.getDataProvider(DataSource[dataSource]).search({ + includeIndices, + query + }) + ); } const searchResults = await Promise.all(promises); 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 f1b56f659..5ba32fc87 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 @@ -156,7 +156,7 @@ export class EodHistoricalDataService implements DataProviderInterface { return !symbol.endsWith('.FOREX'); }) .map((symbol) => { - return this.search(symbol); + return this.search({ query: symbol }); }) ); @@ -219,8 +219,14 @@ export class EodHistoricalDataService implements DataProviderInterface { return 'AAPL.US'; } - public async search(aQuery: string): Promise<{ items: LookupItem[] }> { - const searchResult = await this.getSearchResult(aQuery); + public async search({ + includeIndices, + query + }: { + includeIndices?: boolean; + query: string; + }): Promise<{ items: LookupItem[] }> { + const searchResult = await this.getSearchResult(query); return { items: searchResult diff --git a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts index bcee3c5e7..231d1f09b 100644 --- a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts +++ b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts @@ -143,12 +143,18 @@ export class FinancialModelingPrepService implements DataProviderInterface { return 'AAPL'; } - public async search(aQuery: string): Promise<{ items: LookupItem[] }> { + public async search({ + includeIndices, + query + }: { + includeIndices?: boolean; + query: string; + }): Promise<{ items: LookupItem[] }> { let items: LookupItem[] = []; try { const get = bent( - `${this.URL}/search?query=${aQuery}&apikey=${this.apiKey}`, + `${this.URL}/search?query=${query}&apikey=${this.apiKey}`, 'GET', 'json', 200 diff --git a/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts b/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts index fb182167f..b998e5031 100644 --- a/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts +++ b/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts @@ -153,7 +153,13 @@ export class GoogleSheetsService implements DataProviderInterface { return 'INDEXSP:.INX'; } - public async search(aQuery: string): Promise<{ items: LookupItem[] }> { + public async search({ + includeIndices, + query + }: { + includeIndices?: boolean; + query: string; + }): Promise<{ items: LookupItem[] }> { const items = await this.prismaService.symbolProfile.findMany({ select: { assetClass: true, @@ -169,14 +175,14 @@ export class GoogleSheetsService implements DataProviderInterface { dataSource: this.getName(), name: { mode: 'insensitive', - startsWith: aQuery + startsWith: query } }, { dataSource: this.getName(), symbol: { mode: 'insensitive', - startsWith: aQuery + startsWith: query } } ] diff --git a/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts b/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts index 502e14982..2a16cc24c 100644 --- a/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts +++ b/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts @@ -42,5 +42,11 @@ export interface DataProviderInterface { getTestSymbol(): string; - search(aQuery: string): Promise<{ items: LookupItem[] }>; + search({ + includeIndices, + query + }: { + includeIndices?: boolean; + query: string; + }): Promise<{ items: LookupItem[] }>; } diff --git a/apps/api/src/services/data-provider/manual/manual.service.ts b/apps/api/src/services/data-provider/manual/manual.service.ts index 65035d13f..d8a5a1434 100644 --- a/apps/api/src/services/data-provider/manual/manual.service.ts +++ b/apps/api/src/services/data-provider/manual/manual.service.ts @@ -171,7 +171,13 @@ export class ManualService implements DataProviderInterface { return undefined; } - public async search(aQuery: string): Promise<{ items: LookupItem[] }> { + public async search({ + includeIndices, + query + }: { + includeIndices?: boolean; + query: string; + }): Promise<{ items: LookupItem[] }> { let items = await this.prismaService.symbolProfile.findMany({ select: { assetClass: true, @@ -187,14 +193,14 @@ export class ManualService implements DataProviderInterface { dataSource: this.getName(), name: { mode: 'insensitive', - startsWith: aQuery + startsWith: query } }, { dataSource: this.getName(), symbol: { mode: 'insensitive', - startsWith: aQuery + startsWith: query } } ] diff --git a/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts b/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts index 82b9cbae9..c244df4a3 100644 --- a/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts +++ b/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts @@ -117,7 +117,13 @@ export class RapidApiService implements DataProviderInterface { return undefined; } - public async search(aQuery: string): Promise<{ items: LookupItem[] }> { + public async search({ + includeIndices, + query + }: { + includeIndices?: boolean; + query: string; + }): Promise<{ items: LookupItem[] }> { return { items: [] }; } 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 e47713680..979f684e9 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 @@ -275,11 +275,21 @@ export class YahooFinanceService implements DataProviderInterface { return 'AAPL'; } - public async search(aQuery: string): Promise<{ items: LookupItem[] }> { + public async search({ + includeIndices, + query + }: { + includeIndices?: boolean; + query: string; + }): Promise<{ items: LookupItem[] }> { const items: LookupItem[] = []; try { - const searchResult = await yahooFinance.search(aQuery); + const quoteTypes = ['EQUITY', 'ETF', 'FUTURE', 'MUTUALFUND']; + if (includeIndices) { + quoteTypes.push('INDEX'); + } + const searchResult = await yahooFinance.search(query); const quotes = searchResult.quotes .filter((quote) => { @@ -295,7 +305,7 @@ export class YahooFinanceService implements DataProviderInterface { this.baseCurrency ) )) || - ['EQUITY', 'ETF', 'FUTURE', 'MUTUALFUND'].includes(quoteType) + quoteTypes.includes(quoteType) ); }) .filter(({ quoteType, symbol }) => { diff --git a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html index 16e67bd51..f4c65afed 100644 --- a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html +++ b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -8,7 +8,10 @@
Name, symbol or ISIN - +
diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 32e7ba7ae..822f32ede 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -261,9 +261,17 @@ export class DataService { }); } - public fetchSymbols(aQuery: string) { + public fetchSymbols({ + includeIndices = false, + query + }: { + includeIndices?: boolean; + query: string; + }) { return this.http - .get<{ items: LookupItem[] }>(`/api/v1/symbol/lookup?query=${aQuery}`) + .get<{ items: LookupItem[] }>( + `/api/v1/symbol/lookup?query=${query}&includeIndices=${includeIndices}` + ) .pipe( map((respose) => { return respose.items; diff --git a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts index a6c5b81a1..0521ac827 100644 --- a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts +++ b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts @@ -51,6 +51,7 @@ export class SymbolAutocompleteComponent implements OnInit, OnDestroy { @Input() public isLoading = false; + @Input() private includeIndices = false; @ViewChild(MatInput, { static: false }) private input: MatInput; @@ -94,7 +95,10 @@ export class SymbolAutocompleteComponent this.changeDetectorRef.markForCheck(); }), switchMap((query: string) => { - return this.dataService.fetchSymbols(query); + return this.dataService.fetchSymbols({ + includeIndices: this.includeIndices, + query + }); }) ) .subscribe((filteredLookupItems) => {