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:
parent
217bb6aa5a
commit
ba3cf82c6e
@ -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/),
|
||||
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
|
||||
|
||||
### Changed
|
||||
|
@ -243,7 +243,7 @@ export class BenchmarkService {
|
||||
});
|
||||
|
||||
const exchangeRateAtStartDate =
|
||||
exchangeRates[currentSymbolItem.currency]?.[
|
||||
exchangeRates[`${currentSymbolItem.currency}${userCurrency}`]?.[
|
||||
format(startDate, DATE_FORMAT)
|
||||
];
|
||||
|
||||
@ -275,7 +275,9 @@ export class BenchmarkService {
|
||||
}
|
||||
|
||||
const exchangeRate =
|
||||
exchangeRates[format(marketDataItem.date, DATE_FORMAT)];
|
||||
exchangeRates[`${currentSymbolItem.currency}${userCurrency}`]?.[
|
||||
format(marketDataItem.date, DATE_FORMAT)
|
||||
];
|
||||
|
||||
const exchangeRateFactor =
|
||||
isNumber(exchangeRateAtStartDate) && isNumber(exchangeRate)
|
||||
@ -300,7 +302,10 @@ export class BenchmarkService {
|
||||
);
|
||||
|
||||
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 =
|
||||
isNumber(exchangeRateAtStartDate) && isNumber(exchangeRate)
|
||||
|
@ -298,7 +298,8 @@ export class PortfolioCalculator {
|
||||
start,
|
||||
step,
|
||||
symbol,
|
||||
exchangeRates: exchangeRatesByCurrency[currencies[symbol]],
|
||||
exchangeRates:
|
||||
exchangeRatesByCurrency[`${currencies[symbol]}${this.currency}`],
|
||||
isChartMode: true
|
||||
});
|
||||
|
||||
@ -565,7 +566,11 @@ export class PortfolioCalculator {
|
||||
for (const item of lastTransactionPoint.items) {
|
||||
const marketPriceInBaseCurrency = marketSymbolMap[endDateString]?.[
|
||||
item.symbol
|
||||
]?.mul(exchangeRatesByCurrency[item.currency]?.[endDateString]);
|
||||
]?.mul(
|
||||
exchangeRatesByCurrency[`${item.currency}${this.currency}`]?.[
|
||||
endDateString
|
||||
]
|
||||
);
|
||||
|
||||
const {
|
||||
grossPerformance,
|
||||
@ -585,7 +590,8 @@ export class PortfolioCalculator {
|
||||
end,
|
||||
marketSymbolMap,
|
||||
start,
|
||||
exchangeRates: exchangeRatesByCurrency[item.currency],
|
||||
exchangeRates:
|
||||
exchangeRatesByCurrency[`${item.currency}${this.currency}`],
|
||||
symbol: item.symbol
|
||||
});
|
||||
|
||||
|
@ -33,6 +33,10 @@ export class YahooFinanceDataEnhancerService implements DataEnhancerInterface {
|
||||
symbol = `${DEFAULT_CURRENCY}${symbol}`;
|
||||
}
|
||||
|
||||
if (symbol.includes(`${DEFAULT_CURRENCY}ZAC`)) {
|
||||
symbol = `${DEFAULT_CURRENCY}ZAc`;
|
||||
}
|
||||
|
||||
return symbol.replace('=X', '');
|
||||
}
|
||||
|
||||
|
@ -7,14 +7,14 @@ export const ExchangeRateDataServiceMock = {
|
||||
}): Promise<any> => {
|
||||
if (targetCurrency === 'CHF') {
|
||||
return Promise.resolve({
|
||||
CHF: {
|
||||
CHFCHF: {
|
||||
'2015-01-01': 1,
|
||||
'2017-12-31': 1,
|
||||
'2018-01-01': 1,
|
||||
'2023-01-03': 1,
|
||||
'2023-07-10': 1
|
||||
},
|
||||
USD: {
|
||||
USDCHF: {
|
||||
'2015-01-01': 0.9941099999999999,
|
||||
'2017-12-31': 0.9787,
|
||||
'2018-01-01': 0.97373,
|
||||
|
@ -64,11 +64,12 @@ export class ExchangeRateDataService {
|
||||
} = {};
|
||||
|
||||
for (let currency of currencies) {
|
||||
exchangeRatesByCurrency[currency] = await this.getExchangeRates({
|
||||
startDate,
|
||||
currencyFrom: currency,
|
||||
currencyTo: targetCurrency
|
||||
});
|
||||
exchangeRatesByCurrency[`${currency}${targetCurrency}`] =
|
||||
await this.getExchangeRates({
|
||||
startDate,
|
||||
currencyFrom: currency,
|
||||
currencyTo: targetCurrency
|
||||
});
|
||||
|
||||
let previousExchangeRate = 1;
|
||||
|
||||
@ -82,17 +83,25 @@ export class ExchangeRateDataService {
|
||||
let dateString = format(date, DATE_FORMAT);
|
||||
|
||||
// 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
|
||||
exchangeRatesByCurrency[currency][dateString] = previousExchangeRate;
|
||||
exchangeRatesByCurrency[`${currency}${targetCurrency}`][dateString] =
|
||||
previousExchangeRate;
|
||||
|
||||
Logger.error(
|
||||
`No exchange rate has been found for ${DEFAULT_CURRENCY}${currency} at ${dateString}`,
|
||||
'ExchangeRateDataService'
|
||||
);
|
||||
if (currency === DEFAULT_CURRENCY) {
|
||||
Logger.error(
|
||||
`No exchange rate has been found for ${currency}${targetCurrency} at ${dateString}`,
|
||||
'ExchangeRateDataService'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// If available, update the previous exchange rate
|
||||
previousExchangeRate = exchangeRatesByCurrency[currency][dateString];
|
||||
previousExchangeRate =
|
||||
exchangeRatesByCurrency[`${currency}${targetCurrency}`][dateString];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -136,24 +145,20 @@ export class ExchangeRateDataService {
|
||||
getYesterday()
|
||||
);
|
||||
|
||||
if (Object.keys(result).length !== this.currencyPairs.length) {
|
||||
// Load currencies directly from data provider as a fallback
|
||||
// if historical data is not fully available
|
||||
const quotes = await this.dataProviderService.getQuotes({
|
||||
items: this.currencyPairs.map(({ dataSource, symbol }) => {
|
||||
return { dataSource, symbol };
|
||||
}),
|
||||
requestTimeout: ms('30 seconds')
|
||||
});
|
||||
const quotes = await this.dataProviderService.getQuotes({
|
||||
items: this.currencyPairs.map(({ dataSource, symbol }) => {
|
||||
return { dataSource, symbol };
|
||||
}),
|
||||
requestTimeout: ms('30 seconds')
|
||||
});
|
||||
|
||||
for (const symbol of Object.keys(quotes)) {
|
||||
if (isNumber(quotes[symbol].marketPrice)) {
|
||||
result[symbol] = {
|
||||
[format(getYesterday(), DATE_FORMAT)]: {
|
||||
marketPrice: quotes[symbol].marketPrice
|
||||
}
|
||||
};
|
||||
}
|
||||
for (const symbol of Object.keys(quotes)) {
|
||||
if (isNumber(quotes[symbol].marketPrice)) {
|
||||
result[symbol] = {
|
||||
[format(getYesterday(), DATE_FORMAT)]: {
|
||||
marketPrice: quotes[symbol].marketPrice
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,6 +258,7 @@ export class ExchangeRateDataService {
|
||||
`No exchange rate has been found for ${aFromCurrency}${aToCurrency}`,
|
||||
'ExchangeRateDataService'
|
||||
);
|
||||
|
||||
return aValue;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user