Browse Source

Feature/add fallback to market currency from suffix code when Yahoo Finance API omits currency

pull/4725/head
qvanphong 3 months ago
parent
commit
5169a69e7d
  1. 65
      apps/api/src/assets/stocks/market-currencies.json
  2. 37
      apps/api/src/helper/market-currencies.helper.spec.ts
  3. 37
      apps/api/src/helper/market-currencies.helper.ts
  4. 4
      apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts
  5. 22
      apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts

65
apps/api/src/assets/stocks/market-currencies.json

@ -0,0 +1,65 @@
{
"VN": "VND",
"BK": "THB",
"T": "JPY",
"KQ": "KRW",
"KS": "KRW",
"SS": "CNY",
"SZ": "CNY",
"HK": "HKD",
"L": "GBP",
"IL": "GBP",
"AX": "AUD",
"TO": "CAD",
"V": "CAD",
"NE": "CAD",
"CN": "CAD",
"PA": "EUR",
"NX": "EUR",
"BR": "EUR",
"AS": "EUR",
"IR": "EUR",
"LS": "EUR",
"MC": "EUR",
"SN": "CLP",
"SA": "BRL",
"MX": "MXN",
"BO": "INR",
"NS": "INR",
"JK": "IDR",
"KL": "MYR",
"SI": "SGD",
"TW": "TWD",
"TWO": "TWD",
"CO": "DKK",
"HE": "EUR",
"ST": "SEK",
"SW": "CHF",
"F": "EUR",
"BE": "EUR",
"BM": "EUR",
"DU": "EUR",
"HM": "EUR",
"HA": "EUR",
"MU": "EUR",
"SG": "EUR",
"DE": "EUR",
"AT": "EUR",
"CA": "EGP",
"TA": "ILS",
"MI": "EUR",
"TI": "EUR",
"PR": "CZK",
"BD": "HUF",
"IC": "ISK",
"TL": "EUR",
"RG": "EUR",
"VS": "EUR",
"OL": "NOK",
"QA": "QAR",
"ME": "RUB",
"IS": "TRY",
"SAU": "SAR",
"CR": "VES",
"NZ": "NZD"
}

37
apps/api/src/helper/market-currencies.helper.spec.ts

@ -0,0 +1,37 @@
import { lookupCurrency } from '@ghostfolio/api/helper/market-currencies.helper';
describe('lookupCurrency', () => {
it('should return null if param is empty or null', () => {
expect(lookupCurrency(null)).toBeNull();
expect(lookupCurrency('')).toBeNull();
expect(lookupCurrency(' ')).toBeNull();
});
it('should return corresponding market currency from suffix code', () => {
expect(lookupCurrency('VN')).toStrictEqual('VND');
expect(lookupCurrency('T')).toStrictEqual('JPY');
});
});
describe('determineStockCurrency', () => {
it('should return corresponding market currency from symbol has suffix code', () => {
expect(lookupCurrency('MBB.VN')).toStrictEqual('VND');
expect(lookupCurrency('7203.T')).toStrictEqual('JPY');
expect(lookupCurrency('EA.BK')).toStrictEqual('THB');
});
it('should return null if symbol has no suffix code', () => {
expect(lookupCurrency('APPL')).toBeNull();
expect(lookupCurrency('TLSA')).toBeNull();
});
it('should return null if symbol has suffix code but not supported in json data', () => {
expect(lookupCurrency('A.CBT')).toBeNull();
});
});

37
apps/api/src/helper/market-currencies.helper.ts

@ -0,0 +1,37 @@
const stockExchangeCurrencies: {
[key: string]: string;
} = require('../assets/stocks/market-currencies.json');
/**
*
* @param marketSuffixCode - The market suffix code (e.g. 'BK', 'VN', 'T' etc.)
* @description This function retrieves the currency associated with a given market suffix code from Yahoo Finance.
* It uses a "stock-exchange-currencies.json" file that contains the mapping of market suffix codes to their respective currencies.
* If there is no matching currency for the provided market suffix code, the function returns null.
* Please refer: https://help.yahoo.com/kb/SLN2310.html
*
* @returns string | null - The currency associated with the market suffix code, or null if not found.
*/
export function lookupCurrency(marketSuffixCode: string): string | null {
if (marketSuffixCode in stockExchangeCurrencies) {
return stockExchangeCurrencies[marketSuffixCode];
}
return null;
}
/**
*
* @param symbol - The symbol to determine the market currency for (e.g. 'MBB.VN', 'EA.BK', etc.)
* @description This function extracts the market suffix code from the provided symbol and retrieves the corresponding currency.
* It uses the "getMarketCurrency" function to perform the lookup.
*
* @returns string | null - The currency associated with the market suffix code, or null if not found or the symbol do not have market suffix code.
*/
export function determineStockCurrency(symbol: string): string | null {
const marketSuffixCode = symbol.split('.').pop();
if (!marketSuffixCode) {
return null;
}
return lookupCurrency(marketSuffixCode);
}

4
apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts

@ -1,3 +1,4 @@
import { determineStockCurrency } from '@ghostfolio/api/helper/market-currencies.helper';
import { CryptocurrencyService } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.service';
import { AssetProfileDelistedError } from '@ghostfolio/api/services/data-provider/errors/asset-profile-delisted.error';
import { DataEnhancerInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-enhancer.interface';
@ -190,7 +191,8 @@ export class YahooFinanceDataEnhancerService implements DataEnhancerInterface {
response.assetClass = assetClass;
response.assetSubClass = assetSubClass;
response.currency = assetProfile.price.currency;
response.currency =
assetProfile.price.currency ?? determineStockCurrency(symbol);
response.dataSource = this.getName();
response.name = this.formatName({
longName: assetProfile.price.longName,

22
apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts

@ -1,3 +1,4 @@
import { determineStockCurrency } from '@ghostfolio/api/helper/market-currencies.helper';
import { CryptocurrencyService } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.service';
import { YahooFinanceDataEnhancerService } from '@ghostfolio/api/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service';
import { AssetProfileDelistedError } from '@ghostfolio/api/services/data-provider/errors/asset-profile-delisted.error';
@ -25,6 +26,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { DataSource, SymbolProfile } from '@prisma/client';
import { addDays, format, isSameDay } from 'date-fns';
import YahooFinance from 'yahoo-finance2';
import { ModuleOptionsWithValidateTrue } from 'yahoo-finance2/esm/src/lib/moduleCommon';
import { ChartResultArray } from 'yahoo-finance2/esm/src/modules/chart';
import {
HistoricalDividendsResult,
@ -81,8 +83,12 @@ export class YahooFinanceService implements DataProviderInterface {
events: 'dividends',
interval: granularity === 'month' ? '1mo' : '1d',
period1: format(from, DATE_FORMAT),
period2: format(to, DATE_FORMAT)
}
period2: format(to, DATE_FORMAT),
return: 'array'
},
{
validateResult: determineStockCurrency(symbol) == null
} as ModuleOptionsWithValidateTrue
)
);
const response: {
@ -129,8 +135,12 @@ export class YahooFinanceService implements DataProviderInterface {
{
interval: '1d',
period1: format(from, DATE_FORMAT),
period2: format(to, DATE_FORMAT)
}
period2: format(to, DATE_FORMAT),
return: 'array'
},
{
validateResult: determineStockCurrency(symbol) == null
} as ModuleOptionsWithValidateTrue
)
);
@ -211,7 +221,7 @@ export class YahooFinanceService implements DataProviderInterface {
);
response[symbol] = {
currency: quote.currency,
currency: quote.currency ?? determineStockCurrency(symbol),
dataSource: this.getName(),
marketState:
quote.marketState === 'REGULAR' ||
@ -307,7 +317,7 @@ export class YahooFinanceService implements DataProviderInterface {
assetClass,
assetSubClass,
symbol,
currency: marketDataItem.currency,
currency: marketDataItem.currency ?? determineStockCurrency(symbol),
dataProviderInfo: this.getDataProviderInfo(),
dataSource: this.getName(),
name: this.yahooFinanceDataEnhancerService.formatName({

Loading…
Cancel
Save