Feature/increase robustness of exchange rates by always getting quotes (#2875)

* Always get quotes (with fallback to historical data)

* Update changelog
This commit is contained in:
Thomas Kaul 2024-01-15 19:02:00 +01:00 committed by GitHub
parent 217bb6aa5a
commit ba3cf82c6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 64 additions and 37 deletions

View File

@ -5,6 +5,12 @@ 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
- Inreased the robustness of the exchange rates by always getting quotes in the exchange rate data service
## 2.39.0 - 2024-01-14 ## 2.39.0 - 2024-01-14
### Changed ### Changed

View File

@ -243,7 +243,7 @@ export class BenchmarkService {
}); });
const exchangeRateAtStartDate = const exchangeRateAtStartDate =
exchangeRates[currentSymbolItem.currency]?.[ exchangeRates[`${currentSymbolItem.currency}${userCurrency}`]?.[
format(startDate, DATE_FORMAT) format(startDate, DATE_FORMAT)
]; ];
@ -275,7 +275,9 @@ export class BenchmarkService {
} }
const exchangeRate = const exchangeRate =
exchangeRates[format(marketDataItem.date, DATE_FORMAT)]; exchangeRates[`${currentSymbolItem.currency}${userCurrency}`]?.[
format(marketDataItem.date, DATE_FORMAT)
];
const exchangeRateFactor = const exchangeRateFactor =
isNumber(exchangeRateAtStartDate) && isNumber(exchangeRate) isNumber(exchangeRateAtStartDate) && isNumber(exchangeRate)
@ -300,7 +302,10 @@ export class BenchmarkService {
); );
if (currentSymbolItem?.marketPrice && !includesToday) { if (currentSymbolItem?.marketPrice && !includesToday) {
const exchangeRate = exchangeRates[format(new Date(), DATE_FORMAT)]; const exchangeRate =
exchangeRates[`${currentSymbolItem.currency}${userCurrency}`]?.[
format(new Date(), DATE_FORMAT)
];
const exchangeRateFactor = const exchangeRateFactor =
isNumber(exchangeRateAtStartDate) && isNumber(exchangeRate) isNumber(exchangeRateAtStartDate) && isNumber(exchangeRate)

View File

@ -298,7 +298,8 @@ export class PortfolioCalculator {
start, start,
step, step,
symbol, symbol,
exchangeRates: exchangeRatesByCurrency[currencies[symbol]], exchangeRates:
exchangeRatesByCurrency[`${currencies[symbol]}${this.currency}`],
isChartMode: true isChartMode: true
}); });
@ -565,7 +566,11 @@ export class PortfolioCalculator {
for (const item of lastTransactionPoint.items) { for (const item of lastTransactionPoint.items) {
const marketPriceInBaseCurrency = marketSymbolMap[endDateString]?.[ const marketPriceInBaseCurrency = marketSymbolMap[endDateString]?.[
item.symbol item.symbol
]?.mul(exchangeRatesByCurrency[item.currency]?.[endDateString]); ]?.mul(
exchangeRatesByCurrency[`${item.currency}${this.currency}`]?.[
endDateString
]
);
const { const {
grossPerformance, grossPerformance,
@ -585,7 +590,8 @@ export class PortfolioCalculator {
end, end,
marketSymbolMap, marketSymbolMap,
start, start,
exchangeRates: exchangeRatesByCurrency[item.currency], exchangeRates:
exchangeRatesByCurrency[`${item.currency}${this.currency}`],
symbol: item.symbol symbol: item.symbol
}); });

View File

@ -33,6 +33,10 @@ export class YahooFinanceDataEnhancerService implements DataEnhancerInterface {
symbol = `${DEFAULT_CURRENCY}${symbol}`; symbol = `${DEFAULT_CURRENCY}${symbol}`;
} }
if (symbol.includes(`${DEFAULT_CURRENCY}ZAC`)) {
symbol = `${DEFAULT_CURRENCY}ZAc`;
}
return symbol.replace('=X', ''); return symbol.replace('=X', '');
} }

View File

@ -7,14 +7,14 @@ export const ExchangeRateDataServiceMock = {
}): Promise<any> => { }): Promise<any> => {
if (targetCurrency === 'CHF') { if (targetCurrency === 'CHF') {
return Promise.resolve({ return Promise.resolve({
CHF: { CHFCHF: {
'2015-01-01': 1, '2015-01-01': 1,
'2017-12-31': 1, '2017-12-31': 1,
'2018-01-01': 1, '2018-01-01': 1,
'2023-01-03': 1, '2023-01-03': 1,
'2023-07-10': 1 '2023-07-10': 1
}, },
USD: { USDCHF: {
'2015-01-01': 0.9941099999999999, '2015-01-01': 0.9941099999999999,
'2017-12-31': 0.9787, '2017-12-31': 0.9787,
'2018-01-01': 0.97373, '2018-01-01': 0.97373,

View File

@ -64,11 +64,12 @@ export class ExchangeRateDataService {
} = {}; } = {};
for (let currency of currencies) { for (let currency of currencies) {
exchangeRatesByCurrency[currency] = await this.getExchangeRates({ exchangeRatesByCurrency[`${currency}${targetCurrency}`] =
startDate, await this.getExchangeRates({
currencyFrom: currency, startDate,
currencyTo: targetCurrency currencyFrom: currency,
}); currencyTo: targetCurrency
});
let previousExchangeRate = 1; let previousExchangeRate = 1;
@ -82,17 +83,25 @@ export class ExchangeRateDataService {
let dateString = format(date, DATE_FORMAT); let dateString = format(date, DATE_FORMAT);
// Check if the exchange rate for the current date is missing // Check if the exchange rate for the current date is missing
if (isNaN(exchangeRatesByCurrency[currency][dateString])) { if (
isNaN(
exchangeRatesByCurrency[`${currency}${targetCurrency}`][dateString]
)
) {
// If missing, fill with the previous exchange rate // If missing, fill with the previous exchange rate
exchangeRatesByCurrency[currency][dateString] = previousExchangeRate; exchangeRatesByCurrency[`${currency}${targetCurrency}`][dateString] =
previousExchangeRate;
Logger.error( if (currency === DEFAULT_CURRENCY) {
`No exchange rate has been found for ${DEFAULT_CURRENCY}${currency} at ${dateString}`, Logger.error(
'ExchangeRateDataService' `No exchange rate has been found for ${currency}${targetCurrency} at ${dateString}`,
); 'ExchangeRateDataService'
);
}
} else { } else {
// If available, update the previous exchange rate // If available, update the previous exchange rate
previousExchangeRate = exchangeRatesByCurrency[currency][dateString]; previousExchangeRate =
exchangeRatesByCurrency[`${currency}${targetCurrency}`][dateString];
} }
} }
} }
@ -136,24 +145,20 @@ export class ExchangeRateDataService {
getYesterday() getYesterday()
); );
if (Object.keys(result).length !== this.currencyPairs.length) { const quotes = await this.dataProviderService.getQuotes({
// Load currencies directly from data provider as a fallback items: this.currencyPairs.map(({ dataSource, symbol }) => {
// if historical data is not fully available return { dataSource, symbol };
const quotes = await this.dataProviderService.getQuotes({ }),
items: this.currencyPairs.map(({ dataSource, symbol }) => { requestTimeout: ms('30 seconds')
return { dataSource, symbol }; });
}),
requestTimeout: ms('30 seconds')
});
for (const symbol of Object.keys(quotes)) { for (const symbol of Object.keys(quotes)) {
if (isNumber(quotes[symbol].marketPrice)) { if (isNumber(quotes[symbol].marketPrice)) {
result[symbol] = { result[symbol] = {
[format(getYesterday(), DATE_FORMAT)]: { [format(getYesterday(), DATE_FORMAT)]: {
marketPrice: quotes[symbol].marketPrice marketPrice: quotes[symbol].marketPrice
} }
}; };
}
} }
} }
@ -253,6 +258,7 @@ export class ExchangeRateDataService {
`No exchange rate has been found for ${aFromCurrency}${aToCurrency}`, `No exchange rate has been found for ${aFromCurrency}${aToCurrency}`,
'ExchangeRateDataService' 'ExchangeRateDataService'
); );
return aValue; return aValue;
} }