From f308ae7a13b3f937faae3769bdfe85d5c01f4093 Mon Sep 17 00:00:00 2001 From: Valentin Zickner <3200232+vzickner@users.noreply.github.com> Date: Mon, 11 Oct 2021 13:32:21 -0400 Subject: [PATCH] add sectors and countries for ETFs (#410) * Update changelog Co-Authored-By: Valentin Zickner Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> --- CHANGELOG.md | 4 + apps/api/src/app/cache/cache.module.ts | 15 +-- apps/api/src/app/info/info.module.ts | 14 +-- .../portfolio/current-rate.service.spec.ts | 1 + .../src/services/data-gathering.service.ts | 14 ++- .../alpha-vantage/alpha-vantage.service.ts | 2 +- .../trackinsight/trackinsight.service.ts | 73 +++++++++++++++ .../data-provider/data-provider.module.ts | 9 +- .../data-provider/data-provider.service.ts | 66 ++++++++------ .../ghostfolio-scraper-api.service.ts | 2 +- .../interfaces/data-enhancer.interface.ts | 11 +++ .../interfaces/data-provider.interface.ts | 2 +- .../rakuten-rapid-api.service.ts | 2 +- .../yahoo-finance/yahoo-finance.service.ts | 91 ++++++++++--------- .../api/src/services/interfaces/interfaces.ts | 1 + 15 files changed, 208 insertions(+), 99 deletions(-) create mode 100644 apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts create mode 100644 apps/api/src/services/data-provider/interfaces/data-enhancer.interface.ts rename apps/api/src/services/{ => data-provider}/interfaces/data-provider.interface.ts (94%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27cbc0cb4..7fe7a0440 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 a data enhancer for symbol profile data (countries and sectors) via _Trackinsight_ + ### Changed - Changed the values of the global heat map to fixed-point notation diff --git a/apps/api/src/app/cache/cache.module.ts b/apps/api/src/app/cache/cache.module.ts index bbbd8f108..663d9926b 100644 --- a/apps/api/src/app/cache/cache.module.ts +++ b/apps/api/src/app/cache/cache.module.ts @@ -2,11 +2,7 @@ import { CacheService } from '@ghostfolio/api/app/cache/cache.service'; import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service'; -import { AlphaVantageService } from '@ghostfolio/api/services/data-provider/alpha-vantage/alpha-vantage.service'; -import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; -import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service'; -import { RakutenRapidApiService } from '@ghostfolio/api/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service'; -import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service'; +import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data.module'; import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { Module } from '@nestjs/common'; @@ -14,18 +10,13 @@ import { Module } from '@nestjs/common'; import { CacheController } from './cache.controller'; @Module({ - imports: [ExchangeRateDataModule, RedisCacheModule], + imports: [DataProviderModule, ExchangeRateDataModule, RedisCacheModule], controllers: [CacheController], providers: [ - AlphaVantageService, CacheService, ConfigurationService, DataGatheringService, - DataProviderService, - GhostfolioScraperApiService, - PrismaService, - RakutenRapidApiService, - YahooFinanceService + PrismaService ] }) export class CacheModule {} diff --git a/apps/api/src/app/info/info.module.ts b/apps/api/src/app/info/info.module.ts index c1b8ee21e..d2fb4ff38 100644 --- a/apps/api/src/app/info/info.module.ts +++ b/apps/api/src/app/info/info.module.ts @@ -1,10 +1,6 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service'; -import { AlphaVantageService } from '@ghostfolio/api/services/data-provider/alpha-vantage/alpha-vantage.service'; -import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; -import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service'; -import { RakutenRapidApiService } from '@ghostfolio/api/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service'; -import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service'; +import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data.module'; import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { Module } from '@nestjs/common'; @@ -15,6 +11,7 @@ import { InfoService } from './info.service'; @Module({ imports: [ + DataProviderModule, ExchangeRateDataModule, JwtModule.register({ secret: process.env.JWT_SECRET_KEY, @@ -23,15 +20,10 @@ import { InfoService } from './info.service'; ], controllers: [InfoController], providers: [ - AlphaVantageService, ConfigurationService, DataGatheringService, - DataProviderService, - GhostfolioScraperApiService, InfoService, - PrismaService, - RakutenRapidApiService, - YahooFinanceService + PrismaService ] }) export class InfoModule {} diff --git a/apps/api/src/app/portfolio/current-rate.service.spec.ts b/apps/api/src/app/portfolio/current-rate.service.spec.ts index a166a11af..7795552eb 100644 --- a/apps/api/src/app/portfolio/current-rate.service.spec.ts +++ b/apps/api/src/app/portfolio/current-rate.service.spec.ts @@ -75,6 +75,7 @@ describe('CurrentRateService', () => { dataProviderService = new DataProviderService( null, null, + [], null, null, null, diff --git a/apps/api/src/services/data-gathering.service.ts b/apps/api/src/services/data-gathering.service.ts index 513192b25..35a9a4e63 100644 --- a/apps/api/src/services/data-gathering.service.ts +++ b/apps/api/src/services/data-gathering.service.ts @@ -132,7 +132,15 @@ export class DataGatheringService { for (const [ symbol, - { assetClass, assetSubClass, countries, currency, dataSource, name } + { + assetClass, + assetSubClass, + countries, + currency, + dataSource, + name, + sectors + } ] of Object.entries(currentData)) { try { await this.prismaService.symbolProfile.upsert({ @@ -143,6 +151,7 @@ export class DataGatheringService { currency, dataSource, name, + sectors, symbol }, update: { @@ -150,7 +159,8 @@ export class DataGatheringService { assetSubClass, countries, currency, - name + name, + sectors }, where: { dataSource_symbol: { 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 6bf88feec..6482c4522 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 @@ -6,11 +6,11 @@ import { Injectable } from '@nestjs/common'; import { DataSource } from '@prisma/client'; import { isAfter, isBefore, parse } from 'date-fns'; -import { DataProviderInterface } from '../../interfaces/data-provider.interface'; import { IDataProviderHistoricalResponse, IDataProviderResponse } from '../../interfaces/interfaces'; +import { DataProviderInterface } from '../interfaces/data-provider.interface'; import { IAlphaVantageHistoricalResponse } from './interfaces/interfaces'; @Injectable() diff --git a/apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts b/apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts new file mode 100644 index 000000000..eb018c1c9 --- /dev/null +++ b/apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts @@ -0,0 +1,73 @@ +import { DataEnhancerInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-enhancer.interface'; +import { IDataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +import bent from 'bent'; + +const getJSON = bent('json'); + +export class TrackinsightDataEnhancerService implements DataEnhancerInterface { + private static baseUrl = 'https://data.trackinsight.com/holdings'; + private static countries = require('countries-list/dist/countries.json'); + private static sectorsMapping = { + 'Consumer Discretionary': 'Consumer Cyclical', + 'Consumer Defensive': 'Consumer Staples', + 'Health Care': 'Healthcare', + 'Information Technology': 'Technology' + }; + + public async enhance({ + response, + symbol + }: { + response: IDataProviderResponse; + symbol: string; + }): Promise { + if ( + !(response.assetClass === 'EQUITY' && response.assetSubClass === 'ETF') + ) { + return response; + } + + const holdings = await getJSON( + `${TrackinsightDataEnhancerService.baseUrl}/${symbol}.json` + ).catch(() => { + return getJSON( + `${TrackinsightDataEnhancerService.baseUrl}/${ + symbol.split('.')[0] + }.json` + ); + }); + + if (!response.countries || response.countries.length === 0) { + response.countries = []; + for (const [name, value] of Object.entries(holdings.countries)) { + let countryCode: string; + + for (const [key, country] of Object.entries( + TrackinsightDataEnhancerService.countries + )) { + if (country.name === name) { + countryCode = key; + break; + } + } + + response.countries.push({ + code: countryCode, + weight: value.weight + }); + } + } + + if (!response.sectors || response.sectors.length === 0) { + response.sectors = []; + for (const [name, value] of Object.entries(holdings.sectors)) { + response.sectors.push({ + name: TrackinsightDataEnhancerService.sectorsMapping[name] ?? name, + weight: value.weight + }); + } + } + + return Promise.resolve(response); + } +} diff --git a/apps/api/src/services/data-provider/data-provider.module.ts b/apps/api/src/services/data-provider/data-provider.module.ts index 5c677751d..9e3e57fd4 100644 --- a/apps/api/src/services/data-provider/data-provider.module.ts +++ b/apps/api/src/services/data-provider/data-provider.module.ts @@ -1,4 +1,5 @@ import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module'; +import { TrackinsightDataEnhancerService } from '@ghostfolio/api/services/data-provider/data-enhancer/trackinsight/trackinsight.service'; import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service'; import { RakutenRapidApiService } from '@ghostfolio/api/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service'; import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service'; @@ -15,7 +16,13 @@ import { DataProviderService } from './data-provider.service'; DataProviderService, GhostfolioScraperApiService, RakutenRapidApiService, - YahooFinanceService + TrackinsightDataEnhancerService, + YahooFinanceService, + { + inject: [TrackinsightDataEnhancerService], + provide: 'DataEnhancers', + useFactory: (trackinsight) => [trackinsight] + } ], exports: [DataProviderService, GhostfolioScraperApiService] }) 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 7995eb563..11109ed43 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -1,5 +1,6 @@ import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; +import { DataEnhancerInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-enhancer.interface'; import { IDataGatheringItem, IDataProviderHistoricalResponse, @@ -8,7 +9,7 @@ import { import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { Granularity } from '@ghostfolio/common/types'; -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { DataSource, MarketData } from '@prisma/client'; import { format } from 'date-fns'; import { isEmpty } from 'lodash'; @@ -16,16 +17,15 @@ import { isEmpty } from 'lodash'; import { AlphaVantageService } from './alpha-vantage/alpha-vantage.service'; import { GhostfolioScraperApiService } from './ghostfolio-scraper-api/ghostfolio-scraper-api.service'; import { RakutenRapidApiService } from './rakuten-rapid-api/rakuten-rapid-api.service'; -import { - YahooFinanceService, - convertToYahooFinanceSymbol -} from './yahoo-finance/yahoo-finance.service'; +import { YahooFinanceService } from './yahoo-finance/yahoo-finance.service'; @Injectable() export class DataProviderService { public constructor( private readonly alphaVantageService: AlphaVantageService, private readonly configurationService: ConfigurationService, + @Inject('DataEnhancers') + private readonly dataEnhancers: DataEnhancerInterface[], private readonly ghostfolioScraperApiService: GhostfolioScraperApiService, private readonly prismaService: PrismaService, private readonly rakutenRapidApiService: RakutenRapidApiService, @@ -42,27 +42,35 @@ export class DataProviderService { } = {}; for (const item of items) { - if (item.dataSource === DataSource.ALPHA_VANTAGE) { - response[item.symbol] = ( - await this.alphaVantageService.get([item.symbol]) - )[item.symbol]; - } else if (item.dataSource === DataSource.GHOSTFOLIO) { - response[item.symbol] = ( - await this.ghostfolioScraperApiService.get([item.symbol]) - )[item.symbol]; - } else if (item.dataSource === DataSource.RAKUTEN) { - response[item.symbol] = ( - await this.rakutenRapidApiService.get([item.symbol]) - )[item.symbol]; - } else if (item.dataSource === DataSource.YAHOO) { - response[item.symbol] = ( - await this.yahooFinanceService.get([ - convertToYahooFinanceSymbol(item.symbol) - ]) - )[item.symbol]; + const dataProvider = this.getDataProvider(item.dataSource); + response[item.symbol] = (await dataProvider.get([item.symbol]))[ + item.symbol + ]; + } + + const promises = []; + for (const symbol of Object.keys(response)) { + let promise = Promise.resolve(response[symbol]); + for (const dataEnhancer of this.dataEnhancers) { + promise = promise.then((currentResponse) => + dataEnhancer + .enhance({ symbol, response: currentResponse }) + .catch((error) => { + console.error( + `Failed to enhance data for symbol ${symbol}`, + error + ); + return currentResponse; + }) + ); } + promises.push( + promise.then((currentResponse) => (response[symbol] = currentResponse)) + ); } + await Promise.all(promises); + return response; } @@ -103,11 +111,13 @@ export class DataProviderService { }); try { - const queryRaw = `SELECT * FROM "MarketData" WHERE "dataSource" IN ('${dataSources.join( - `','` - )}') AND "symbol" IN ('${symbols.join( - `','` - )}') ${granularityQuery} ${rangeQuery} ORDER BY date;`; + const queryRaw = `SELECT * + FROM "MarketData" + WHERE "dataSource" IN ('${dataSources.join(`','`)}') + AND "symbol" IN ('${symbols.join( + `','` + )}') ${granularityQuery} ${rangeQuery} + ORDER BY date;`; const marketDataByGranularity: MarketData[] = await this.prismaService.$queryRaw(queryRaw); diff --git a/apps/api/src/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service.ts b/apps/api/src/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service.ts index 805a4edcb..dd240e8b3 100644 --- a/apps/api/src/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service.ts +++ b/apps/api/src/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service.ts @@ -12,13 +12,13 @@ import * as bent from 'bent'; import * as cheerio from 'cheerio'; import { format } from 'date-fns'; -import { DataProviderInterface } from '../../interfaces/data-provider.interface'; import { IDataGatheringItem, IDataProviderHistoricalResponse, IDataProviderResponse, MarketState } from '../../interfaces/interfaces'; +import { DataProviderInterface } from '../interfaces/data-provider.interface'; import { ScraperConfig } from './interfaces/scraper-config.interface'; @Injectable() diff --git a/apps/api/src/services/data-provider/interfaces/data-enhancer.interface.ts b/apps/api/src/services/data-provider/interfaces/data-enhancer.interface.ts new file mode 100644 index 000000000..1a0d4f87a --- /dev/null +++ b/apps/api/src/services/data-provider/interfaces/data-enhancer.interface.ts @@ -0,0 +1,11 @@ +import { IDataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; + +export interface DataEnhancerInterface { + enhance({ + response, + symbol + }: { + response: IDataProviderResponse; + symbol: string; + }): Promise; +} diff --git a/apps/api/src/services/interfaces/data-provider.interface.ts b/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts similarity index 94% rename from apps/api/src/services/interfaces/data-provider.interface.ts rename to apps/api/src/services/data-provider/interfaces/data-provider.interface.ts index b36be4cb4..864dbcf52 100644 --- a/apps/api/src/services/interfaces/data-provider.interface.ts +++ b/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts @@ -4,7 +4,7 @@ import { Granularity } from '@ghostfolio/common/types'; import { IDataProviderHistoricalResponse, IDataProviderResponse -} from './interfaces'; +} from '../../interfaces/interfaces'; export interface DataProviderInterface { canHandle(symbol: string): boolean; diff --git a/apps/api/src/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service.ts b/apps/api/src/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service.ts index aad7dbd0f..b6b5642b6 100644 --- a/apps/api/src/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service.ts +++ b/apps/api/src/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service.ts @@ -14,12 +14,12 @@ import { DataSource } from '@prisma/client'; import * as bent from 'bent'; import { format, subMonths, subWeeks, subYears } from 'date-fns'; -import { DataProviderInterface } from '../../interfaces/data-provider.interface'; import { IDataProviderHistoricalResponse, IDataProviderResponse, MarketState } from '../../interfaces/interfaces'; +import { DataProviderInterface } from '../interfaces/data-provider.interface'; @Injectable() export class RakutenRapidApiService implements DataProviderInterface { 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 c32d543e1..d1f155f6c 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 @@ -10,12 +10,12 @@ import { countries } from 'countries-list'; import { format } from 'date-fns'; import * as yahooFinance from 'yahoo-finance'; -import { DataProviderInterface } from '../../interfaces/data-provider.interface'; import { IDataProviderHistoricalResponse, IDataProviderResponse, MarketState } from '../../interfaces/interfaces'; +import { DataProviderInterface } from '../interfaces/data-provider.interface'; import { IYahooFinanceHistoricalResponse, IYahooFinancePrice, @@ -33,11 +33,14 @@ export class YahooFinanceService implements DataProviderInterface { } public async get( - aYahooFinanceSymbols: string[] + aSymbols: string[] ): Promise<{ [symbol: string]: IDataProviderResponse }> { - if (aYahooFinanceSymbols.length <= 0) { + if (aSymbols.length <= 0) { return {}; } + const yahooFinanceSymbols = aSymbols.map((symbol) => + this.convertToYahooFinanceSymbol(symbol) + ); try { const response: { [symbol: string]: IDataProviderResponse } = {}; @@ -46,12 +49,12 @@ export class YahooFinanceService implements DataProviderInterface { [symbol: string]: IYahooFinanceQuoteResponse; } = await yahooFinance.quote({ modules: ['price', 'summaryProfile'], - symbols: aYahooFinanceSymbols + symbols: yahooFinanceSymbols }); for (const [yahooFinanceSymbol, value] of Object.entries(data)) { // Convert symbols back - const symbol = convertFromYahooFinanceSymbol(yahooFinanceSymbol); + const symbol = this.convertFromYahooFinanceSymbol(yahooFinanceSymbol); const { assetClass, assetSubClass } = this.parseAssetClass(value.price); @@ -93,6 +96,12 @@ export class YahooFinanceService implements DataProviderInterface { response[symbol].countries = [{ code, weight: 1 }]; } } catch {} + + if (value.summaryProfile?.sector) { + response[symbol].sectors = [ + { name: value.summaryProfile?.sector, weight: 1 } + ]; + } } // Add url if available @@ -123,7 +132,7 @@ export class YahooFinanceService implements DataProviderInterface { } const yahooFinanceSymbols = aSymbols.map((symbol) => { - return convertToYahooFinanceSymbol(symbol); + return this.convertToYahooFinanceSymbol(symbol); }); try { @@ -143,7 +152,7 @@ export class YahooFinanceService implements DataProviderInterface { historicalData )) { // Convert symbols back - const symbol = convertFromYahooFinanceSymbol(yahooFinanceSymbol); + const symbol = this.convertFromYahooFinanceSymbol(yahooFinanceSymbol); response[symbol] = {}; timeSeries.forEach((timeSerie) => { @@ -214,6 +223,40 @@ export class YahooFinanceService implements DataProviderInterface { return { items }; } + private convertFromYahooFinanceSymbol(aYahooFinanceSymbol: string) { + const symbol = aYahooFinanceSymbol.replace('-', ''); + return symbol.replace('=X', ''); + } + + /** + * Converts a symbol to a Yahoo Finance symbol + * + * Currency: USDCHF -> USDCHF=X + * Cryptocurrency: BTCUSD -> BTC-USD + * DOGEUSD -> DOGE-USD + * SOL1USD -> SOL1-USD + */ + private convertToYahooFinanceSymbol(aSymbol: string) { + if ( + (aSymbol.includes('CHF') || + aSymbol.includes('EUR') || + aSymbol.includes('USD')) && + aSymbol.length >= 6 + ) { + if (isCurrency(aSymbol.substring(0, aSymbol.length - 3))) { + return `${aSymbol}=X`; + } else if (isCrypto(aSymbol) || isCrypto(aSymbol.replace('1', ''))) { + // Add a dash before the last three characters + // BTCUSD -> BTC-USD + // DOGEUSD -> DOGE-USD + // SOL1USD -> SOL1-USD + return aSymbol.replace('USD', '-USD'); + } + } + + return aSymbol; + } + private parseAssetClass(aPrice: IYahooFinancePrice): { assetClass: AssetClass; assetSubClass: AssetSubClass; @@ -247,37 +290,3 @@ export class YahooFinanceService implements DataProviderInterface { return aString; } } - -export const convertFromYahooFinanceSymbol = (aYahooFinanceSymbol: string) => { - const symbol = aYahooFinanceSymbol.replace('-', ''); - return symbol.replace('=X', ''); -}; - -/** - * Converts a symbol to a Yahoo Finance symbol - * - * Currency: USDCHF -> USDCHF=X - * Cryptocurrency: BTCUSD -> BTC-USD - * DOGEUSD -> DOGE-USD - * SOL1USD -> SOL1-USD - */ -export const convertToYahooFinanceSymbol = (aSymbol: string) => { - if ( - (aSymbol.includes('CHF') || - aSymbol.includes('EUR') || - aSymbol.includes('USD')) && - aSymbol.length >= 6 - ) { - if (isCurrency(aSymbol.substring(0, aSymbol.length - 3))) { - return `${aSymbol}=X`; - } else if (isCrypto(aSymbol) || isCrypto(aSymbol.replace('1', ''))) { - // Add a dash before the last three characters - // BTCUSD -> BTC-USD - // DOGEUSD -> DOGE-USD - // SOL1USD -> SOL1-USD - return aSymbol.replace('USD', '-USD'); - } - } - - return aSymbol; -}; diff --git a/apps/api/src/services/interfaces/interfaces.ts b/apps/api/src/services/interfaces/interfaces.ts index a7d2e8d9f..d4913a83b 100644 --- a/apps/api/src/services/interfaces/interfaces.ts +++ b/apps/api/src/services/interfaces/interfaces.ts @@ -45,6 +45,7 @@ export interface IDataProviderResponse { marketPrice: number; marketState: MarketState; name?: string; + sectors?: { name: string; weight: number }[]; url?: string; }