Browse Source
Add ability to search for indices and fix gf-symbol-autocomplete validation (#2094)
* Bugfix/Fix gf-symbol-autocomplete validation
* Feature/Add ability to search for indices
* Update changelog
pull/2104/head^2
Arghya Ghosh
2 years ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with
132 additions and
32 deletions
-
CHANGELOG.md
-
apps/api/src/app/symbol/symbol.controller.ts
-
apps/api/src/app/symbol/symbol.service.ts
-
apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts
-
apps/api/src/services/data-provider/coingecko/coingecko.service.ts
-
apps/api/src/services/data-provider/data-provider.service.ts
-
apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts
-
apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts
-
apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts
-
apps/api/src/services/data-provider/interfaces/data-provider.interface.ts
-
apps/api/src/services/data-provider/manual/manual.service.ts
-
apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts
-
apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts
-
apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html
-
apps/client/src/app/services/data.service.ts
-
libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts
|
|
@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. |
|
|
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), |
|
|
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |
|
|
|
|
|
|
|
## Unreleased |
|
|
|
|
|
|
|
### Added |
|
|
|
|
|
|
|
- Added the ability to add an index for benchmarks as an asset profile in the admin control panel |
|
|
|
|
|
|
|
### Fixed |
|
|
|
|
|
|
|
- Fixed an issue with the clone functionality of a transaction caused by the symbol search component |
|
|
|
|
|
|
|
## 1.283.5 - 2023-06-25 |
|
|
|
|
|
|
|
### Added |
|
|
|
|
|
@ -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, |
|
|
|
query: query.toLowerCase(), |
|
|
|
user: this.request.user |
|
|
|
}); |
|
|
|
|
|
@ -81,9 +81,11 @@ export class SymbolService { |
|
|
|
} |
|
|
|
|
|
|
|
public async lookup({ |
|
|
|
includeIndices = false, |
|
|
|
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 |
|
|
|
}); |
|
|
|
|
|
@ -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 = false, |
|
|
|
query |
|
|
|
}: { |
|
|
|
includeIndices?: boolean; |
|
|
|
query: string; |
|
|
|
}): Promise<{ items: LookupItem[] }> { |
|
|
|
const result = await this.alphaVantage.data.search(query); |
|
|
|
|
|
|
|
return { |
|
|
|
items: result?.bestMatches?.map((bestMatch) => { |
|
|
|
|
|
@ -164,16 +164,17 @@ export class CoinGeckoService implements DataProviderInterface { |
|
|
|
return 'bitcoin'; |
|
|
|
} |
|
|
|
|
|
|
|
public async search(aQuery: string): Promise<{ items: LookupItem[] }> { |
|
|
|
public async search({ |
|
|
|
includeIndices = false, |
|
|
|
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 }) => { |
|
|
|
|
|
@ -367,9 +367,11 @@ export class DataProviderService { |
|
|
|
} |
|
|
|
|
|
|
|
public async search({ |
|
|
|
includeIndices = false, |
|
|
|
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); |
|
|
|
|
|
@ -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 = false, |
|
|
|
query |
|
|
|
}: { |
|
|
|
includeIndices?: boolean; |
|
|
|
query: string; |
|
|
|
}): Promise<{ items: LookupItem[] }> { |
|
|
|
const searchResult = await this.getSearchResult(query); |
|
|
|
|
|
|
|
return { |
|
|
|
items: searchResult |
|
|
|
|
|
@ -143,12 +143,18 @@ export class FinancialModelingPrepService implements DataProviderInterface { |
|
|
|
return 'AAPL'; |
|
|
|
} |
|
|
|
|
|
|
|
public async search(aQuery: string): Promise<{ items: LookupItem[] }> { |
|
|
|
public async search({ |
|
|
|
includeIndices = false, |
|
|
|
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 |
|
|
|
|
|
@ -153,7 +153,13 @@ export class GoogleSheetsService implements DataProviderInterface { |
|
|
|
return 'INDEXSP:.INX'; |
|
|
|
} |
|
|
|
|
|
|
|
public async search(aQuery: string): Promise<{ items: LookupItem[] }> { |
|
|
|
public async search({ |
|
|
|
includeIndices = false, |
|
|
|
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 |
|
|
|
} |
|
|
|
} |
|
|
|
] |
|
|
|
|
|
@ -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[] }>; |
|
|
|
} |
|
|
|
|
|
@ -171,7 +171,13 @@ export class ManualService implements DataProviderInterface { |
|
|
|
return undefined; |
|
|
|
} |
|
|
|
|
|
|
|
public async search(aQuery: string): Promise<{ items: LookupItem[] }> { |
|
|
|
public async search({ |
|
|
|
includeIndices = false, |
|
|
|
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 |
|
|
|
} |
|
|
|
} |
|
|
|
] |
|
|
|
|
|
@ -117,7 +117,13 @@ export class RapidApiService implements DataProviderInterface { |
|
|
|
return undefined; |
|
|
|
} |
|
|
|
|
|
|
|
public async search(aQuery: string): Promise<{ items: LookupItem[] }> { |
|
|
|
public async search({ |
|
|
|
includeIndices = false, |
|
|
|
query |
|
|
|
}: { |
|
|
|
includeIndices?: boolean; |
|
|
|
query: string; |
|
|
|
}): Promise<{ items: LookupItem[] }> { |
|
|
|
return { items: [] }; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -275,11 +275,23 @@ export class YahooFinanceService implements DataProviderInterface { |
|
|
|
return 'AAPL'; |
|
|
|
} |
|
|
|
|
|
|
|
public async search(aQuery: string): Promise<{ items: LookupItem[] }> { |
|
|
|
public async search({ |
|
|
|
includeIndices = false, |
|
|
|
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 +307,7 @@ export class YahooFinanceService implements DataProviderInterface { |
|
|
|
this.baseCurrency |
|
|
|
) |
|
|
|
)) || |
|
|
|
['EQUITY', 'ETF', 'FUTURE', 'MUTUALFUND'].includes(quoteType) |
|
|
|
quoteTypes.includes(quoteType) |
|
|
|
); |
|
|
|
}) |
|
|
|
.filter(({ quoteType, symbol }) => { |
|
|
|
|
|
@ -8,7 +8,10 @@ |
|
|
|
<div class="flex-grow-1 py-3" mat-dialog-content> |
|
|
|
<mat-form-field appearance="outline" class="w-100"> |
|
|
|
<mat-label i18n>Name, symbol or ISIN</mat-label> |
|
|
|
<gf-symbol-autocomplete formControlName="searchSymbol" /> |
|
|
|
<gf-symbol-autocomplete |
|
|
|
formControlName="searchSymbol" |
|
|
|
[includeIndices]="true" |
|
|
|
/> |
|
|
|
</mat-form-field> |
|
|
|
</div> |
|
|
|
<div class="d-flex justify-content-end" mat-dialog-actions> |
|
|
|
|
|
@ -261,9 +261,21 @@ export class DataService { |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
public fetchSymbols(aQuery: string) { |
|
|
|
public fetchSymbols({ |
|
|
|
includeIndices = false, |
|
|
|
query |
|
|
|
}: { |
|
|
|
includeIndices?: boolean; |
|
|
|
query: string; |
|
|
|
}) { |
|
|
|
let params = new HttpParams().set('query', query); |
|
|
|
|
|
|
|
if (includeIndices) { |
|
|
|
params = params.append('includeIndices', includeIndices); |
|
|
|
} |
|
|
|
|
|
|
|
return this.http |
|
|
|
.get<{ items: LookupItem[] }>(`/api/v1/symbol/lookup?query=${aQuery}`) |
|
|
|
.get<{ items: LookupItem[] }>('/api/v1/symbol/lookup', { params }) |
|
|
|
.pipe( |
|
|
|
map((respose) => { |
|
|
|
return respose.items; |
|
|
|
|
|
@ -50,6 +50,7 @@ export class SymbolAutocompleteComponent |
|
|
|
extends AbstractMatFormField<LookupItem> |
|
|
|
implements OnInit, OnDestroy |
|
|
|
{ |
|
|
|
@Input() private includeIndices = false; |
|
|
|
@Input() public isLoading = 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({ |
|
|
|
query, |
|
|
|
includeIndices: this.includeIndices |
|
|
|
}); |
|
|
|
}) |
|
|
|
) |
|
|
|
.subscribe((filteredLookupItems) => { |
|
|
@ -132,7 +136,11 @@ export class SymbolAutocompleteComponent |
|
|
|
public ngDoCheck() { |
|
|
|
if (this.ngControl) { |
|
|
|
this.validateRequired(); |
|
|
|
|
|
|
|
if (this.control.touched) { |
|
|
|
this.validateSelection(); |
|
|
|
} |
|
|
|
|
|
|
|
this.errorState = this.ngControl.invalid && this.ngControl.touched; |
|
|
|
this.stateChanges.next(); |
|
|
|
} |
|
|
|