Browse Source

Improve handling of future liabilities (#3118)

* Improve handling of future liabilities

* Refactor currentValue to currentValueInBaseCurrency

* Update changelog
pull/3120/head
Thomas Kaul 11 months ago
committed by GitHub
parent
commit
bc8d8309d4
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      CHANGELOG.md
  2. 2
      apps/api/src/app/portfolio/current-rate.service.mock.ts
  3. 4
      apps/api/src/app/portfolio/interfaces/current-positions.interface.ts
  4. 5
      apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell.spec.ts
  5. 5
      apps/api/src/app/portfolio/portfolio-calculator-baln-buy.spec.ts
  6. 5
      apps/api/src/app/portfolio/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
  7. 5
      apps/api/src/app/portfolio/portfolio-calculator-googl-buy.spec.ts
  8. 2
      apps/api/src/app/portfolio/portfolio-calculator-no-orders.spec.ts
  9. 5
      apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
  10. 5
      apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell.spec.ts
  11. 87
      apps/api/src/app/portfolio/portfolio-calculator.ts
  12. 25
      apps/api/src/app/portfolio/portfolio.service.ts
  13. 1
      apps/api/src/services/exchange-rate-data/exchange-rate-data.service.mock.ts
  14. 22
      apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts
  15. 1
      libs/common/src/lib/interfaces/timeline-position.interface.ts

4
CHANGELOG.md

@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Integrated dividend into the transaction point concept in the portfolio service - Integrated dividend into the transaction point concept in the portfolio service
- Removed the environment variable `WEB_AUTH_RP_ID` - Removed the environment variable `WEB_AUTH_RP_ID`
### Fixed
- Fixed an issue in the calculation of the portfolio summary caused by future liabilities
## 2.61.1 - 2024-03-06 ## 2.61.1 - 2024-03-06
### Fixed ### Fixed

2
apps/api/src/app/portfolio/current-rate.service.mock.ts

@ -46,6 +46,8 @@ function mockGetValue(symbol: string, date: Date) {
case 'MSFT': case 'MSFT':
if (isSameDay(parseDate('2021-09-16'), date)) { if (isSameDay(parseDate('2021-09-16'), date)) {
return { marketPrice: 89.12 }; return { marketPrice: 89.12 };
} else if (isSameDay(parseDate('2021-11-16'), date)) {
return { marketPrice: 339.51 };
} else if (isSameDay(parseDate('2023-07-10'), date)) { } else if (isSameDay(parseDate('2023-07-10'), date)) {
return { marketPrice: 331.83 }; return { marketPrice: 331.83 };
} }

4
apps/api/src/app/portfolio/interfaces/current-positions.interface.ts

@ -3,7 +3,7 @@ import { ResponseError, TimelinePosition } from '@ghostfolio/common/interfaces';
import Big from 'big.js'; import Big from 'big.js';
export interface CurrentPositions extends ResponseError { export interface CurrentPositions extends ResponseError {
positions: TimelinePosition[]; currentValueInBaseCurrency: Big;
grossPerformance: Big; grossPerformance: Big;
grossPerformanceWithCurrencyEffect: Big; grossPerformanceWithCurrencyEffect: Big;
grossPerformancePercentage: Big; grossPerformancePercentage: Big;
@ -14,6 +14,6 @@ export interface CurrentPositions extends ResponseError {
netPerformanceWithCurrencyEffect: Big; netPerformanceWithCurrencyEffect: Big;
netPerformancePercentage: Big; netPerformancePercentage: Big;
netPerformancePercentageWithCurrencyEffect: Big; netPerformancePercentageWithCurrencyEffect: Big;
currentValue: Big; positions: TimelinePosition[];
totalInvestment: Big; totalInvestment: Big;
} }

5
apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell.spec.ts

@ -87,7 +87,7 @@ describe('PortfolioCalculator', () => {
spy.mockRestore(); spy.mockRestore();
expect(currentPositions).toEqual({ expect(currentPositions).toEqual({
currentValue: new Big('0'), currentValueInBaseCurrency: new Big('0'),
errors: [], errors: [],
grossPerformance: new Big('-12.6'), grossPerformance: new Big('-12.6'),
grossPerformancePercentage: new Big('-0.0440867739678096571'), grossPerformancePercentage: new Big('-0.0440867739678096571'),
@ -131,7 +131,8 @@ describe('PortfolioCalculator', () => {
symbol: 'BALN.SW', symbol: 'BALN.SW',
timeWeightedInvestment: new Big('285.8'), timeWeightedInvestment: new Big('285.8'),
timeWeightedInvestmentWithCurrencyEffect: new Big('285.8'), timeWeightedInvestmentWithCurrencyEffect: new Big('285.8'),
transactionCount: 2 transactionCount: 2,
valueInBaseCurrency: new Big('0')
} }
], ],
totalInvestment: new Big('0'), totalInvestment: new Big('0'),

5
apps/api/src/app/portfolio/portfolio-calculator-baln-buy.spec.ts

@ -76,7 +76,7 @@ describe('PortfolioCalculator', () => {
spy.mockRestore(); spy.mockRestore();
expect(currentPositions).toEqual({ expect(currentPositions).toEqual({
currentValue: new Big('297.8'), currentValueInBaseCurrency: new Big('297.8'),
errors: [], errors: [],
grossPerformance: new Big('24.6'), grossPerformance: new Big('24.6'),
grossPerformancePercentage: new Big('0.09004392386530014641'), grossPerformancePercentage: new Big('0.09004392386530014641'),
@ -120,7 +120,8 @@ describe('PortfolioCalculator', () => {
symbol: 'BALN.SW', symbol: 'BALN.SW',
timeWeightedInvestment: new Big('273.2'), timeWeightedInvestment: new Big('273.2'),
timeWeightedInvestmentWithCurrencyEffect: new Big('273.2'), timeWeightedInvestmentWithCurrencyEffect: new Big('273.2'),
transactionCount: 1 transactionCount: 1,
valueInBaseCurrency: new Big('297.8')
} }
], ],
totalInvestment: new Big('273.2'), totalInvestment: new Big('273.2'),

5
apps/api/src/app/portfolio/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts

@ -100,7 +100,7 @@ describe('PortfolioCalculator', () => {
spy.mockRestore(); spy.mockRestore();
expect(currentPositions).toEqual({ expect(currentPositions).toEqual({
currentValue: new Big('13298.425356'), currentValueInBaseCurrency: new Big('13298.425356'),
errors: [], errors: [],
grossPerformance: new Big('27172.74'), grossPerformance: new Big('27172.74'),
grossPerformancePercentage: new Big('42.41978276196153750666'), grossPerformancePercentage: new Big('42.41978276196153750666'),
@ -151,7 +151,8 @@ describe('PortfolioCalculator', () => {
timeWeightedInvestmentWithCurrencyEffect: new Big( timeWeightedInvestmentWithCurrencyEffect: new Big(
'636.79469348020066587024' '636.79469348020066587024'
), ),
transactionCount: 2 transactionCount: 2,
valueInBaseCurrency: new Big('13298.425356')
} }
], ],
totalInvestment: new Big('320.43'), totalInvestment: new Big('320.43'),

5
apps/api/src/app/portfolio/portfolio-calculator-googl-buy.spec.ts

@ -89,7 +89,7 @@ describe('PortfolioCalculator', () => {
spy.mockRestore(); spy.mockRestore();
expect(currentPositions).toEqual({ expect(currentPositions).toEqual({
currentValue: new Big('103.10483'), currentValueInBaseCurrency: new Big('103.10483'),
errors: [], errors: [],
grossPerformance: new Big('27.33'), grossPerformance: new Big('27.33'),
grossPerformancePercentage: new Big('0.3066651705565529623'), grossPerformancePercentage: new Big('0.3066651705565529623'),
@ -134,7 +134,8 @@ describe('PortfolioCalculator', () => {
tags: undefined, tags: undefined,
timeWeightedInvestment: new Big('89.12'), timeWeightedInvestment: new Big('89.12'),
timeWeightedInvestmentWithCurrencyEffect: new Big('82.329056'), timeWeightedInvestmentWithCurrencyEffect: new Big('82.329056'),
transactionCount: 1 transactionCount: 1,
valueInBaseCurrency: new Big('103.10483')
} }
], ],
totalInvestment: new Big('89.12'), totalInvestment: new Big('89.12'),

2
apps/api/src/app/portfolio/portfolio-calculator-no-orders.spec.ts

@ -64,7 +64,7 @@ describe('PortfolioCalculator', () => {
spy.mockRestore(); spy.mockRestore();
expect(currentPositions).toEqual({ expect(currentPositions).toEqual({
currentValue: new Big(0), currentValueInBaseCurrency: new Big(0),
grossPerformance: new Big(0), grossPerformance: new Big(0),
grossPerformancePercentage: new Big(0), grossPerformancePercentage: new Big(0),
grossPerformancePercentageWithCurrencyEffect: new Big(0), grossPerformancePercentageWithCurrencyEffect: new Big(0),

5
apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts

@ -87,7 +87,7 @@ describe('PortfolioCalculator', () => {
spy.mockRestore(); spy.mockRestore();
expect(currentPositions).toEqual({ expect(currentPositions).toEqual({
currentValue: new Big('87.8'), currentValueInBaseCurrency: new Big('87.8'),
errors: [], errors: [],
grossPerformance: new Big('21.93'), grossPerformance: new Big('21.93'),
grossPerformancePercentage: new Big('0.15113417083448194384'), grossPerformancePercentage: new Big('0.15113417083448194384'),
@ -133,7 +133,8 @@ describe('PortfolioCalculator', () => {
timeWeightedInvestmentWithCurrencyEffect: new Big( timeWeightedInvestmentWithCurrencyEffect: new Big(
'145.10285714285714285714' '145.10285714285714285714'
), ),
transactionCount: 2 transactionCount: 2,
valueInBaseCurrency: new Big('87.8')
} }
], ],
totalInvestment: new Big('75.80'), totalInvestment: new Big('75.80'),

5
apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell.spec.ts

@ -113,7 +113,7 @@ describe('PortfolioCalculator', () => {
}); });
expect(currentPositions).toEqual({ expect(currentPositions).toEqual({
currentValue: new Big('0'), currentValueInBaseCurrency: new Big('0'),
errors: [], errors: [],
grossPerformance: new Big('19.86'), grossPerformance: new Big('19.86'),
grossPerformancePercentage: new Big('0.13100263852242744063'), grossPerformancePercentage: new Big('0.13100263852242744063'),
@ -157,7 +157,8 @@ describe('PortfolioCalculator', () => {
symbol: 'NOVN.SW', symbol: 'NOVN.SW',
timeWeightedInvestment: new Big('151.6'), timeWeightedInvestment: new Big('151.6'),
timeWeightedInvestmentWithCurrencyEffect: new Big('151.6'), timeWeightedInvestmentWithCurrencyEffect: new Big('151.6'),
transactionCount: 2 transactionCount: 2,
valueInBaseCurrency: new Big('0')
} }
], ],
totalInvestment: new Big('0'), totalInvestment: new Big('0'),

87
apps/api/src/app/portfolio/portfolio-calculator.ts

@ -22,6 +22,7 @@ import {
format, format,
isBefore, isBefore,
isSameDay, isSameDay,
max,
subDays subDays
} from 'date-fns'; } from 'date-fns';
import { cloneDeep, first, isNumber, last, sortBy, uniq } from 'lodash'; import { cloneDeep, first, isNumber, last, sortBy, uniq } from 'lodash';
@ -449,16 +450,27 @@ export class PortfolioCalculator {
public async getCurrentPositions( public async getCurrentPositions(
start: Date, start: Date,
end = new Date(Date.now()) end?: Date
): Promise<CurrentPositions> { ): Promise<CurrentPositions> {
const transactionPointsBeforeEndDate = const lastTransactionPoint = last(this.transactionPoints);
this.transactionPoints?.filter((transactionPoint) => {
return isBefore(parseDate(transactionPoint.date), end); let endDate = end;
}) ?? [];
if (!endDate) {
endDate = new Date(Date.now());
if (!transactionPointsBeforeEndDate.length) { if (lastTransactionPoint) {
endDate = max([endDate, parseDate(lastTransactionPoint.date)]);
}
}
const transactionPoints = this.transactionPoints?.filter(({ date }) => {
return isBefore(parseDate(date), endDate);
});
if (!transactionPoints.length) {
return { return {
currentValue: new Big(0), currentValueInBaseCurrency: new Big(0),
grossPerformance: new Big(0), grossPerformance: new Big(0),
grossPerformancePercentage: new Big(0), grossPerformancePercentage: new Big(0),
grossPerformancePercentageWithCurrencyEffect: new Big(0), grossPerformancePercentageWithCurrencyEffect: new Big(0),
@ -473,41 +485,40 @@ export class PortfolioCalculator {
}; };
} }
const lastTransactionPoint =
transactionPointsBeforeEndDate[transactionPointsBeforeEndDate.length - 1];
const currencies: { [symbol: string]: string } = {}; const currencies: { [symbol: string]: string } = {};
const dataGatheringItems: IDataGatheringItem[] = []; const dataGatheringItems: IDataGatheringItem[] = [];
let dates: Date[] = []; let dates: Date[] = [];
let firstIndex = transactionPointsBeforeEndDate.length; let firstIndex = transactionPoints.length;
let firstTransactionPoint: TransactionPoint = null; let firstTransactionPoint: TransactionPoint = null;
dates.push(resetHours(start)); dates.push(resetHours(start));
for (const item of transactionPointsBeforeEndDate[firstIndex - 1].items) {
for (const { currency, dataSource, symbol } of transactionPoints[
firstIndex - 1
].items) {
dataGatheringItems.push({ dataGatheringItems.push({
dataSource: item.dataSource, dataSource,
symbol: item.symbol symbol
}); });
currencies[item.symbol] = item.currency; currencies[symbol] = currency;
} }
for (let i = 0; i < transactionPointsBeforeEndDate.length; i++) { for (let i = 0; i < transactionPoints.length; i++) {
if ( if (
!isBefore(parseDate(transactionPointsBeforeEndDate[i].date), start) && !isBefore(parseDate(transactionPoints[i].date), start) &&
firstTransactionPoint === null firstTransactionPoint === null
) { ) {
firstTransactionPoint = transactionPointsBeforeEndDate[i]; firstTransactionPoint = transactionPoints[i];
firstIndex = i; firstIndex = i;
} }
if (firstTransactionPoint !== null) { if (firstTransactionPoint !== null) {
dates.push( dates.push(resetHours(parseDate(transactionPoints[i].date)));
resetHours(parseDate(transactionPointsBeforeEndDate[i].date))
);
} }
} }
dates.push(resetHours(end)); dates.push(resetHours(endDate));
// Add dates of last week for fallback // Add dates of last week for fallback
dates.push(subDays(resetHours(new Date()), 7)); dates.push(subDays(resetHours(new Date()), 7));
@ -534,7 +545,7 @@ export class PortfolioCalculator {
let exchangeRatesByCurrency = let exchangeRatesByCurrency =
await this.exchangeRateDataService.getExchangeRatesByCurrency({ await this.exchangeRateDataService.getExchangeRatesByCurrency({
currencies: uniq(Object.values(currencies)), currencies: uniq(Object.values(currencies)),
endDate: endOfDay(end), endDate: endOfDay(endDate),
startDate: parseDate(this.transactionPoints?.[0]?.date), startDate: parseDate(this.transactionPoints?.[0]?.date),
targetCurrency: this.currency targetCurrency: this.currency
}); });
@ -570,7 +581,7 @@ export class PortfolioCalculator {
} }
} }
const endDateString = format(end, DATE_FORMAT); const endDateString = format(endDate, DATE_FORMAT);
if (firstIndex > 0) { if (firstIndex > 0) {
firstIndex--; firstIndex--;
@ -582,9 +593,9 @@ export class PortfolioCalculator {
const errors: ResponseError['errors'] = []; const errors: ResponseError['errors'] = [];
for (const item of lastTransactionPoint.items) { for (const item of lastTransactionPoint.items) {
const marketPriceInBaseCurrency = marketSymbolMap[endDateString]?.[ const marketPriceInBaseCurrency = (
item.symbol marketSymbolMap[endDateString]?.[item.symbol] ?? item.averagePrice
]?.mul( ).mul(
exchangeRatesByCurrency[`${item.currency}${this.currency}`]?.[ exchangeRatesByCurrency[`${item.currency}${this.currency}`]?.[
endDateString endDateString
] ]
@ -607,9 +618,9 @@ export class PortfolioCalculator {
totalInvestment, totalInvestment,
totalInvestmentWithCurrencyEffect totalInvestmentWithCurrencyEffect
} = this.getSymbolMetrics({ } = this.getSymbolMetrics({
end,
marketSymbolMap, marketSymbolMap,
start, start,
end: endDate,
exchangeRates: exchangeRates:
exchangeRatesByCurrency[`${item.currency}${this.currency}`], exchangeRatesByCurrency[`${item.currency}${this.currency}`],
symbol: item.symbol symbol: item.symbol
@ -656,7 +667,10 @@ export class PortfolioCalculator {
quantity: item.quantity, quantity: item.quantity,
symbol: item.symbol, symbol: item.symbol,
tags: item.tags, tags: item.tags,
transactionCount: item.transactionCount transactionCount: item.transactionCount,
valueInBaseCurrency: new Big(marketPriceInBaseCurrency).mul(
item.quantity
)
}); });
if ( if (
@ -725,7 +739,7 @@ export class PortfolioCalculator {
} }
private calculateOverallPerformance(positions: TimelinePosition[]) { private calculateOverallPerformance(positions: TimelinePosition[]) {
let currentValue = new Big(0); let currentValueInBaseCurrency = new Big(0);
let grossPerformance = new Big(0); let grossPerformance = new Big(0);
let grossPerformanceWithCurrencyEffect = new Big(0); let grossPerformanceWithCurrencyEffect = new Big(0);
let hasErrors = false; let hasErrors = false;
@ -737,14 +751,9 @@ export class PortfolioCalculator {
let totalTimeWeightedInvestmentWithCurrencyEffect = new Big(0); let totalTimeWeightedInvestmentWithCurrencyEffect = new Big(0);
for (const currentPosition of positions) { for (const currentPosition of positions) {
if ( if (currentPosition.valueInBaseCurrency) {
currentPosition.investment && currentValueInBaseCurrency = currentValueInBaseCurrency.plus(
currentPosition.marketPriceInBaseCurrency currentPosition.valueInBaseCurrency
) {
currentValue = currentValue.plus(
new Big(currentPosition.marketPriceInBaseCurrency).mul(
currentPosition.quantity
)
); );
} else { } else {
hasErrors = true; hasErrors = true;
@ -801,7 +810,7 @@ export class PortfolioCalculator {
} }
return { return {
currentValue, currentValueInBaseCurrency,
grossPerformance, grossPerformance,
grossPerformanceWithCurrencyEffect, grossPerformanceWithCurrencyEffect,
hasErrors, hasErrors,

25
apps/api/src/app/portfolio/portfolio.service.ts

@ -378,9 +378,10 @@ export class PortfolioService {
}); });
const holdings: PortfolioDetails['holdings'] = {}; const holdings: PortfolioDetails['holdings'] = {};
const totalValueInBaseCurrency = currentPositions.currentValue.plus( const totalValueInBaseCurrency =
cashDetails.balanceInBaseCurrency currentPositions.currentValueInBaseCurrency.plus(
); cashDetails.balanceInBaseCurrency
);
const isFilteredByAccount = const isFilteredByAccount =
filters?.some((filter) => { filters?.some((filter) => {
@ -389,7 +390,7 @@ export class PortfolioService {
let filteredValueInBaseCurrency = isFilteredByAccount let filteredValueInBaseCurrency = isFilteredByAccount
? totalValueInBaseCurrency ? totalValueInBaseCurrency
: currentPositions.currentValue; : currentPositions.currentValueInBaseCurrency;
if ( if (
filters?.length === 0 || filters?.length === 0 ||
@ -444,14 +445,14 @@ export class PortfolioService {
quantity, quantity,
symbol, symbol,
tags, tags,
transactionCount transactionCount,
valueInBaseCurrency
} of currentPositions.positions) { } of currentPositions.positions) {
if (quantity.eq(0)) { if (quantity.eq(0)) {
// Ignore positions without any quantity // Ignore positions without any quantity
continue; continue;
} }
const value = quantity.mul(marketPriceInBaseCurrency ?? 0);
const symbolProfile = symbolProfileMap[symbol]; const symbolProfile = symbolProfileMap[symbol];
const dataProviderResponse = dataProviderResponses[symbol]; const dataProviderResponse = dataProviderResponses[symbol];
@ -517,11 +518,11 @@ export class PortfolioService {
} }
} else { } else {
markets[UNKNOWN_KEY] = new Big(markets[UNKNOWN_KEY]) markets[UNKNOWN_KEY] = new Big(markets[UNKNOWN_KEY])
.plus(value) .plus(valueInBaseCurrency)
.toNumber(); .toNumber();
marketsAdvanced[UNKNOWN_KEY] = new Big(marketsAdvanced[UNKNOWN_KEY]) marketsAdvanced[UNKNOWN_KEY] = new Big(marketsAdvanced[UNKNOWN_KEY])
.plus(value) .plus(valueInBaseCurrency)
.toNumber(); .toNumber();
} }
@ -535,7 +536,7 @@ export class PortfolioService {
transactionCount, transactionCount,
allocationInPercentage: filteredValueInBaseCurrency.eq(0) allocationInPercentage: filteredValueInBaseCurrency.eq(0)
? 0 ? 0
: value.div(filteredValueInBaseCurrency).toNumber(), : valueInBaseCurrency.div(filteredValueInBaseCurrency).toNumber(),
assetClass: symbolProfile.assetClass, assetClass: symbolProfile.assetClass,
assetSubClass: symbolProfile.assetSubClass, assetSubClass: symbolProfile.assetSubClass,
countries: symbolProfile.countries, countries: symbolProfile.countries,
@ -560,7 +561,7 @@ export class PortfolioService {
quantity: quantity.toNumber(), quantity: quantity.toNumber(),
sectors: symbolProfile.sectors, sectors: symbolProfile.sectors,
url: symbolProfile.url, url: symbolProfile.url,
valueInBaseCurrency: value.toNumber() valueInBaseCurrency: valueInBaseCurrency.toNumber()
}; };
} }
@ -1175,7 +1176,7 @@ export class PortfolioService {
const startDate = this.getStartDate(dateRange, portfolioStart); const startDate = this.getStartDate(dateRange, portfolioStart);
const { const {
currentValue, currentValueInBaseCurrency,
errors, errors,
grossPerformance, grossPerformance,
grossPerformancePercentage, grossPerformancePercentage,
@ -1270,7 +1271,7 @@ export class PortfolioService {
currentNetPerformancePercentWithCurrencyEffect.toNumber(), currentNetPerformancePercentWithCurrencyEffect.toNumber(),
currentNetPerformanceWithCurrencyEffect: currentNetPerformanceWithCurrencyEffect:
currentNetPerformanceWithCurrencyEffect.toNumber(), currentNetPerformanceWithCurrencyEffect.toNumber(),
currentValue: currentValue.toNumber(), currentValue: currentValueInBaseCurrency.toNumber(),
totalInvestment: totalInvestment.toNumber() totalInvestment: totalInvestment.toNumber()
} }
}; };

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

@ -26,6 +26,7 @@ export const ExchangeRateDataServiceMock = {
return Promise.resolve({ return Promise.resolve({
USDUSD: { USDUSD: {
'2018-01-01': 1, '2018-01-01': 1,
'2021-11-16': 1,
'2023-07-10': 1 '2023-07-10': 1
} }
}); });

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

@ -73,7 +73,17 @@ export class ExchangeRateDataService {
currencyTo: targetCurrency currencyTo: targetCurrency
}); });
let previousExchangeRate = 1; const dateStrings = Object.keys(
exchangeRatesByCurrency[`${currency}${targetCurrency}`]
);
const lastDateString = dateStrings.reduce((a, b) => {
return a > b ? a : b;
});
let previousExchangeRate =
exchangeRatesByCurrency[`${currency}${targetCurrency}`]?.[
lastDateString
] ?? 1;
// Start from the most recent date and fill in missing exchange rates // Start from the most recent date and fill in missing exchange rates
// using the latest available rate // using the latest available rate
@ -94,7 +104,7 @@ export class ExchangeRateDataService {
exchangeRatesByCurrency[`${currency}${targetCurrency}`][dateString] = exchangeRatesByCurrency[`${currency}${targetCurrency}`][dateString] =
previousExchangeRate; previousExchangeRate;
if (currency === DEFAULT_CURRENCY) { if (currency === DEFAULT_CURRENCY && isBefore(date, new Date())) {
Logger.error( Logger.error(
`No exchange rate has been found for ${currency}${targetCurrency} at ${dateString}`, `No exchange rate has been found for ${currency}${targetCurrency} at ${dateString}`,
'ExchangeRateDataService' 'ExchangeRateDataService'
@ -433,13 +443,17 @@ export class ExchangeRateDataService {
]) * ]) *
marketPriceBaseCurrencyToCurrency[format(date, DATE_FORMAT)]; marketPriceBaseCurrencyToCurrency[format(date, DATE_FORMAT)];
factors[format(date, DATE_FORMAT)] = factor; if (isNaN(factor)) {
throw new Error('Exchange rate is not a number');
} else {
factors[format(date, DATE_FORMAT)] = factor;
}
} catch { } catch {
Logger.error( Logger.error(
`No exchange rate has been found for ${currencyFrom}${currencyTo} at ${format( `No exchange rate has been found for ${currencyFrom}${currencyTo} at ${format(
date, date,
DATE_FORMAT DATE_FORMAT
)}`, )}. Please complement market data for ${DEFAULT_CURRENCY}${currencyFrom} and ${DEFAULT_CURRENCY}${currencyTo}.`,
'ExchangeRateDataService' 'ExchangeRateDataService'
); );
} }

1
libs/common/src/lib/interfaces/timeline-position.interface.ts

@ -27,4 +27,5 @@ export interface TimelinePosition {
timeWeightedInvestment: Big; timeWeightedInvestment: Big;
timeWeightedInvestmentWithCurrencyEffect: Big; timeWeightedInvestmentWithCurrencyEffect: Big;
transactionCount: number; transactionCount: number;
valueInBaseCurrency: Big;
} }

Loading…
Cancel
Save