Browse Source

Merge cdc103d60d into 90c90e179e

pull/8245/merge
Andrea Bugeja 16 hours ago
committed by GitHub
parent
commit
4897e1d29c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      CHANGELOG.md
  2. 69
      apps/api/src/services/exchange-rate-data/exchange-rate-data.service.spec.ts
  3. 29
      apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts

4
CHANGELOG.md

@ -56,6 +56,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed the missing localization of the date picker in the asset profile details dialog of the admin control panel
- Fixed the missing tooltip of the client of the last request in the users table of the admin control panel
### Fixed
- Fixed the historical currency conversion of derived currencies (e.g. `GBp`) by converting via their root currency
## 3.88.0 - 2026-10-17
### Added

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

@ -0,0 +1,69 @@
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { parseDate } from '@ghostfolio/common/helper';
import { DataSource } from '@prisma/client';
import { ExchangeRateDataService } from './exchange-rate-data.service';
describe('ExchangeRateDataService', () => {
const date = parseDate('2026-01-02');
let exchangeRateDataService: ExchangeRateDataService;
beforeEach(() => {
// Only the root currency pairs exist, no derived pairs like USDGBp
const marketPrices: { [symbol: string]: number } = {
USDEUR: 0.9,
USDGBP: 0.8
};
exchangeRateDataService = new ExchangeRateDataService(
{
getDataSourceForExchangeRates: () => {
return DataSource.YAHOO;
}
} as unknown as DataProviderService,
{
get: jest.fn(({ symbol }: { symbol: string }) => {
return Promise.resolve(
marketPrices[symbol] ? { marketPrice: marketPrices[symbol] } : null
);
})
} as unknown as MarketDataService,
null,
null
);
});
describe('toCurrencyAtDate', () => {
it('converts a derived currency to its root currency', async () => {
await expect(
exchangeRateDataService.toCurrencyAtDate(1000, 'GBp', 'GBP', date)
).resolves.toBeCloseTo(10);
});
it('converts a root currency to its derived currency', async () => {
await expect(
exchangeRateDataService.toCurrencyAtDate(10, 'GBP', 'GBp', date)
).resolves.toBeCloseTo(1000);
});
it('converts a derived currency indirectly via the root currency', async () => {
await expect(
exchangeRateDataService.toCurrencyAtDate(8000, 'GBp', 'EUR', date)
).resolves.toBeCloseTo(90);
});
it('converts to a derived currency indirectly via the root currency', async () => {
await expect(
exchangeRateDataService.toCurrencyAtDate(90, 'EUR', 'GBp', date)
).resolves.toBeCloseTo(8000);
});
it('keeps the value of the same derived currency', async () => {
await expect(
exchangeRateDataService.toCurrencyAtDate(1.15, 'GBp', 'GBp', date)
).resolves.toBe(1.15);
});
});
});

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

@ -296,18 +296,39 @@ export class ExchangeRateDataService {
return 0;
}
const derivedFromCurrency = DERIVED_CURRENCIES.find(({ currency }) => {
return currency === aFromCurrency;
});
const derivedToCurrency = DERIVED_CURRENCIES.find(({ currency }) => {
return currency === aToCurrency;
});
if (
aFromCurrency !== aToCurrency &&
(derivedFromCurrency || derivedToCurrency)
) {
// Convert via the root currencies, since the exchange rates of derived
// currencies are not available for every currency pair
const valueInRootCurrency = await this.toCurrencyAtDate(
aValue / (derivedFromCurrency?.factor ?? 1),
derivedFromCurrency?.rootCurrency ?? aFromCurrency,
derivedToCurrency?.rootCurrency ?? aToCurrency,
aDate
);
return valueInRootCurrency === undefined
? undefined
: valueInRootCurrency * (derivedToCurrency?.factor ?? 1);
}
if (isToday(aDate)) {
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();

Loading…
Cancel
Save