Browse Source

Bugfix/exchange rate calculation when converting derived currencies (#5961)

* Fix exchange rate calculation when converting derived currencies

* Update changelog
pull/5987/head
Sven Günther 4 days ago
committed by GitHub
parent
commit
e1e455da86
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      CHANGELOG.md
  2. 204
      apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts

6
CHANGELOG.md

@ -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
### Fixed
- Fixed an issue with the exchange rate calculation when converting between derived currencies and their root currencies
## 2.219.0 - 2025-11-23
### Added

204
apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts

@ -30,6 +30,7 @@ import ms from 'ms';
export class ExchangeRateDataService {
private currencies: string[] = [];
private currencyPairs: DataGatheringItem[] = [];
private derivedCurrencyFactors: { [currencyPair: string]: number } = {};
private exchangeRates: { [currencyPair: string]: number } = {};
public constructor(
@ -135,8 +136,14 @@ export class ExchangeRateDataService {
public async initialize() {
this.currencies = await this.prepareCurrencies();
this.currencyPairs = [];
this.derivedCurrencyFactors = {};
this.exchangeRates = {};
for (const { currency, factor, rootCurrency } of DERIVED_CURRENCIES) {
this.derivedCurrencyFactors[`${currency}${rootCurrency}`] = 1 / factor;
this.derivedCurrencyFactors[`${rootCurrency}${currency}`] = factor;
}
for (const {
currency1,
currency2,
@ -266,10 +273,14 @@ export class ExchangeRateDataService {
return this.toCurrency(aValue, aFromCurrency, aToCurrency);
}
const derivedCurrencyFactor =
this.derivedCurrencyFactors[`${aFromCurrency}${aToCurrency}`];
let factor: number;
if (aFromCurrency === aToCurrency) {
factor = 1;
} else if (derivedCurrencyFactor) {
factor = derivedCurrencyFactor;
} else {
const dataSource =
this.dataProviderService.getDataSourceForExchangeRates();
@ -357,111 +368,120 @@ export class ExchangeRateDataService {
for (const date of dates) {
factors[format(date, DATE_FORMAT)] = 1;
}
} else {
const dataSource =
this.dataProviderService.getDataSourceForExchangeRates();
const symbol = `${currencyFrom}${currencyTo}`;
const marketData = await this.marketDataService.getRange({
assetProfileIdentifiers: [
{
dataSource,
symbol
}
],
dateQuery: { gte: startDate, lt: endDate }
});
return factors;
}
const derivedCurrencyFactor =
this.derivedCurrencyFactors[`${currencyFrom}${currencyTo}`];
if (derivedCurrencyFactor) {
for (const date of dates) {
factors[format(date, DATE_FORMAT)] = derivedCurrencyFactor;
}
if (marketData?.length > 0) {
for (const { date, marketPrice } of marketData) {
factors[format(date, DATE_FORMAT)] = marketPrice;
return factors;
}
const dataSource = this.dataProviderService.getDataSourceForExchangeRates();
const symbol = `${currencyFrom}${currencyTo}`;
const marketData = await this.marketDataService.getRange({
assetProfileIdentifiers: [
{
dataSource,
symbol
}
} else {
// Calculate indirectly via base currency
],
dateQuery: { gte: startDate, lt: endDate }
});
const marketPriceBaseCurrencyFromCurrency: {
[dateString: string]: number;
} = {};
const marketPriceBaseCurrencyToCurrency: {
[dateString: string]: number;
} = {};
if (marketData?.length > 0) {
for (const { date, marketPrice } of marketData) {
factors[format(date, DATE_FORMAT)] = marketPrice;
}
} else {
// Calculate indirectly via base currency
const marketPriceBaseCurrencyFromCurrency: {
[dateString: string]: number;
} = {};
const marketPriceBaseCurrencyToCurrency: {
[dateString: string]: number;
} = {};
try {
if (currencyFrom === DEFAULT_CURRENCY) {
for (const date of dates) {
marketPriceBaseCurrencyFromCurrency[format(date, DATE_FORMAT)] = 1;
}
} else {
const marketData = await this.marketDataService.getRange({
assetProfileIdentifiers: [
{
dataSource,
symbol: `${DEFAULT_CURRENCY}${currencyFrom}`
}
],
dateQuery: { gte: startDate, lt: endDate }
});
try {
if (currencyFrom === DEFAULT_CURRENCY) {
for (const date of dates) {
marketPriceBaseCurrencyFromCurrency[format(date, DATE_FORMAT)] =
1;
}
} else {
const marketData = await this.marketDataService.getRange({
assetProfileIdentifiers: [
{
dataSource,
symbol: `${DEFAULT_CURRENCY}${currencyFrom}`
}
],
dateQuery: { gte: startDate, lt: endDate }
});
for (const { date, marketPrice } of marketData) {
marketPriceBaseCurrencyFromCurrency[format(date, DATE_FORMAT)] =
marketPrice;
}
for (const { date, marketPrice } of marketData) {
marketPriceBaseCurrencyFromCurrency[format(date, DATE_FORMAT)] =
marketPrice;
}
} catch {}
}
} catch {}
try {
if (currencyTo === DEFAULT_CURRENCY) {
for (const date of dates) {
marketPriceBaseCurrencyToCurrency[format(date, DATE_FORMAT)] = 1;
}
} else {
const marketData = await this.marketDataService.getRange({
assetProfileIdentifiers: [
{
dataSource,
symbol: `${DEFAULT_CURRENCY}${currencyTo}`
}
],
dateQuery: {
gte: startDate,
lt: endDate
try {
if (currencyTo === DEFAULT_CURRENCY) {
for (const date of dates) {
marketPriceBaseCurrencyToCurrency[format(date, DATE_FORMAT)] = 1;
}
} else {
const marketData = await this.marketDataService.getRange({
assetProfileIdentifiers: [
{
dataSource,
symbol: `${DEFAULT_CURRENCY}${currencyTo}`
}
});
for (const { date, marketPrice } of marketData) {
marketPriceBaseCurrencyToCurrency[format(date, DATE_FORMAT)] =
marketPrice;
],
dateQuery: {
gte: startDate,
lt: endDate
}
});
for (const { date, marketPrice } of marketData) {
marketPriceBaseCurrencyToCurrency[format(date, DATE_FORMAT)] =
marketPrice;
}
} catch {}
}
} catch {}
for (const date of dates) {
try {
const factor =
(1 /
marketPriceBaseCurrencyFromCurrency[
format(date, DATE_FORMAT)
]) *
marketPriceBaseCurrencyToCurrency[format(date, DATE_FORMAT)];
if (isNaN(factor)) {
throw new Error('Exchange rate is not a number');
} else {
factors[format(date, DATE_FORMAT)] = factor;
}
} catch {
let errorMessage = `No exchange rate has been found for ${currencyFrom}${currencyTo} at ${format(
date,
DATE_FORMAT
)}. Please complement market data for ${DEFAULT_CURRENCY}${currencyFrom}`;
if (DEFAULT_CURRENCY !== currencyTo) {
errorMessage = `${errorMessage} and ${DEFAULT_CURRENCY}${currencyTo}`;
}
for (const date of dates) {
try {
const factor =
(1 /
marketPriceBaseCurrencyFromCurrency[format(date, DATE_FORMAT)]) *
marketPriceBaseCurrencyToCurrency[format(date, DATE_FORMAT)];
Logger.error(`${errorMessage}.`, 'ExchangeRateDataService');
if (isNaN(factor)) {
throw new Error('Exchange rate is not a number');
} else {
factors[format(date, DATE_FORMAT)] = factor;
}
} catch {
let errorMessage = `No exchange rate has been found for ${currencyFrom}${currencyTo} at ${format(
date,
DATE_FORMAT
)}. Please complement market data for ${DEFAULT_CURRENCY}${currencyFrom}`;
if (DEFAULT_CURRENCY !== currencyTo) {
errorMessage = `${errorMessage} and ${DEFAULT_CURRENCY}${currencyTo}`;
}
Logger.error(`${errorMessage}.`, 'ExchangeRateDataService');
}
}
}

Loading…
Cancel
Save