diff --git a/apps/api/src/services/cryptocurrency/cryptocurrency.service.ts b/apps/api/src/services/cryptocurrency/cryptocurrency.service.ts index 1ae645208..d5b9fec5b 100644 --- a/apps/api/src/services/cryptocurrency/cryptocurrency.service.ts +++ b/apps/api/src/services/cryptocurrency/cryptocurrency.service.ts @@ -10,7 +10,7 @@ export class CryptocurrencyService { public constructor() {} - public isCrypto(aSymbol = '') { + public isCryptocurrency(aSymbol = '') { const cryptocurrencySymbol = aSymbol.substring(0, aSymbol.length - 3); return this.getCryptocurrencies().includes(cryptocurrencySymbol); } diff --git a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.spec.ts b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.spec.ts new file mode 100644 index 000000000..414a83dd6 --- /dev/null +++ b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.spec.ts @@ -0,0 +1,64 @@ +import { CryptocurrencyService } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.service'; +import { YahooFinanceService } from './yahoo-finance.service'; + +jest.mock( + '@ghostfolio/api/services/cryptocurrency/cryptocurrency.service', + () => { + return { + CryptocurrencyService: jest.fn().mockImplementation(() => { + return { + isCryptocurrency: (symbol: string) => { + switch (symbol) { + case 'BTCUSD': + return true; + case 'DOGEUSD': + return true; + case 'SOLUSD': + return true; + default: + return false; + } + } + }; + }) + }; + } +); + +describe('YahooFinanceService', () => { + let cryptocurrencyService: CryptocurrencyService; + let yahooFinanceService: YahooFinanceService; + + beforeAll(async () => { + cryptocurrencyService = new CryptocurrencyService(); + + yahooFinanceService = new YahooFinanceService(cryptocurrencyService); + }); + + it('convertFromYahooFinanceSymbol', async () => { + expect( + await yahooFinanceService.convertFromYahooFinanceSymbol('BRK-B') + ).toEqual('BRK-B'); + expect( + await yahooFinanceService.convertFromYahooFinanceSymbol('BTC-USD') + ).toEqual('BTCUSD'); + expect( + await yahooFinanceService.convertFromYahooFinanceSymbol('EURUSD=X') + ).toEqual('EURUSD'); + }); + + it('convertToYahooFinanceSymbol', async () => { + expect( + await yahooFinanceService.convertToYahooFinanceSymbol('BTCUSD') + ).toEqual('BTC-USD'); + expect( + await yahooFinanceService.convertToYahooFinanceSymbol('DOGEUSD') + ).toEqual('DOGE-USD'); + expect( + await yahooFinanceService.convertToYahooFinanceSymbol('SOL1USD') + ).toEqual('SOL1-USD'); + expect( + await yahooFinanceService.convertToYahooFinanceSymbol('USDCHF') + ).toEqual('USDCHF=X'); + }); +}); 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 8b9afebee..c9d5081d8 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,6 +1,6 @@ import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { CryptocurrencyService } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.service'; -import { UNKNOWN_KEY } from '@ghostfolio/common/config'; +import { baseCurrency, UNKNOWN_KEY } from '@ghostfolio/common/config'; import { DATE_FORMAT, isCurrency } from '@ghostfolio/common/helper'; import { Granularity } from '@ghostfolio/common/types'; import { Injectable, Logger } from '@nestjs/common'; @@ -35,6 +35,47 @@ export class YahooFinanceService implements DataProviderInterface { return true; } + public convertFromYahooFinanceSymbol(aYahooFinanceSymbol: string) { + const symbol = aYahooFinanceSymbol.replace( + new RegExp(`-${baseCurrency}$`), + baseCurrency + ); + 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 + */ + public convertToYahooFinanceSymbol(aSymbol: string) { + if (aSymbol.includes(baseCurrency) && aSymbol.length >= 6) { + if (isCurrency(aSymbol.substring(0, aSymbol.length - 3))) { + return `${aSymbol}=X`; + } else if ( + this.cryptocurrencyService.isCryptocurrency( + aSymbol + .replace(new RegExp(`-${baseCurrency}$`), baseCurrency) + .replace('1', '') + ) + ) { + // Add a dash before the last three characters + // BTCUSD -> BTC-USD + // DOGEUSD -> DOGE-USD + // SOL1USD -> SOL1-USD + return aSymbol.replace( + new RegExp(`-?${baseCurrency}$`), + `-${baseCurrency}` + ); + } + } + + return aSymbol; + } + public async get( aSymbols: string[] ): Promise<{ [symbol: string]: IDataProviderResponse }> { @@ -69,7 +110,7 @@ export class YahooFinanceService implements DataProviderInterface { exchange: this.parseExchange(value.price?.exchangeName), marketState: value.price?.marketState === 'REGULAR' || - this.cryptocurrencyService.isCrypto(symbol) + this.cryptocurrencyService.isCryptocurrency(symbol) ? MarketState.open : MarketState.closed, marketPrice: value.price?.regularMarketPrice || 0, @@ -204,8 +245,10 @@ export class YahooFinanceService implements DataProviderInterface { .filter(({ quoteType, symbol }) => { return ( (quoteType === 'CRYPTOCURRENCY' && - this.cryptocurrencyService.isCrypto( - symbol.replace(new RegExp('-USD$'), 'USD').replace('1', '') + this.cryptocurrencyService.isCryptocurrency( + symbol + .replace(new RegExp(`-${baseCurrency}$`), baseCurrency) + .replace('1', '') )) || quoteType === 'EQUITY' || quoteType === 'ETF' @@ -213,9 +256,9 @@ export class YahooFinanceService implements DataProviderInterface { }) .filter(({ quoteType, symbol }) => { if (quoteType === 'CRYPTOCURRENCY') { - // Only allow cryptocurrencies in USD to avoid having redundancy in the database. - // Trades need to be converted manually before to USD (or a UI converter needs to be developed) - return symbol.includes('USD'); + // Only allow cryptocurrencies in base currency to avoid having redundancy in the database. + // Transactions need to be converted manually to the base currency before + return symbol.includes(baseCurrency); } return true; @@ -239,44 +282,6 @@ export class YahooFinanceService implements DataProviderInterface { return { items }; } - private convertFromYahooFinanceSymbol(aYahooFinanceSymbol: string) { - const symbol = aYahooFinanceSymbol.replace('-USD', 'USD'); - 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 ( - this.cryptocurrencyService.isCrypto( - aSymbol.replace(new RegExp('-USD$'), 'USD').replace('1', '') - ) - ) { - // Add a dash before the last three characters - // BTCUSD -> BTC-USD - // DOGEUSD -> DOGE-USD - // SOL1USD -> SOL1-USD - return aSymbol.replace(new RegExp('-?USD$'), '-USD'); - } - } - - return aSymbol; - } - private parseAssetClass(aPrice: IYahooFinancePrice): { assetClass: AssetClass; assetSubClass: AssetSubClass;