Browse Source

Fix portfolio calculation for holdings with same symbol

pull/7664/head
Thomas Kaul 3 days ago
parent
commit
5a1fb49521
  1. 2
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  2. 9
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-from-two-data-sources.spec.ts
  3. 32
      apps/api/src/app/portfolio/current-rate.service.mock.ts

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

@ -1109,7 +1109,7 @@ export abstract class PortfolioCalculator {
const items = lastTransactionPoint?.items ?? [];
const newItems = items.filter((item) => {
return item.dataSource !== dataSource || item.symbol !== symbol;
return getAssetProfileIdentifier(item) !== assetProfileIdentifier;
});
newItems.push(currentTransactionPointItem);

9
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-from-two-data-sources.spec.ts

@ -142,16 +142,17 @@ describe('PortfolioCalculator', () => {
const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
// The holdings must not be aggregated, because they belong to two
// different asset profiles
// different asset profiles. Each holding must be valuated with the
// market price of its own data source.
expect(portfolioSnapshot.positions).toEqual([
expect.objectContaining({
activitiesCount: 1,
dataSource: 'EOD_HISTORICAL_DATA',
investment: new Big('679.02'),
marketPrice: 331.83,
marketPrice: 332.47,
quantity: new Big('2'),
symbol: 'MSFT',
valueInBaseCurrency: new Big('663.66')
valueInBaseCurrency: new Big('664.94')
}),
expect.objectContaining({
activitiesCount: 1,
@ -165,7 +166,7 @@ describe('PortfolioCalculator', () => {
]);
expect(portfolioSnapshot.currentValueInBaseCurrency).toEqual(
new Big('995.49')
new Big('996.77')
);
expect(portfolioSnapshot.totalInvestment).toEqual(new Big('1018.53'));

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

@ -1,5 +1,6 @@
import { parseDate, resetHours } from '@ghostfolio/common/helper';
import { DataSource } from '@prisma/client';
import {
addDays,
eachDayOfInterval,
@ -12,7 +13,15 @@ import { GetValueObject } from './interfaces/get-value-object.interface';
import { GetValuesObject } from './interfaces/get-values-object.interface';
import { GetValuesParams } from './interfaces/get-values-params.interface';
function mockGetValue(symbol: string, date: Date) {
function mockGetValue({
dataSource,
date,
symbol
}: {
dataSource: DataSource;
date: Date;
symbol: string;
}) {
switch (symbol) {
case '55196015-1365-4560-aa60-8751ae6d18f8':
if (isSameDay(parseDate('2022-01-31'), date)) {
@ -83,7 +92,12 @@ function mockGetValue(symbol: string, date: Date) {
} else if (isSameDay(parseDate('2023-07-09'), date)) {
return { marketPrice: 337.22 };
} else if (isSameDay(parseDate('2023-07-10'), date)) {
return { marketPrice: 331.83 };
// Deviating market prices per data source to verify that the market
// price is resolved by the asset profile identifier
return {
marketPrice:
dataSource === DataSource.EOD_HISTORICAL_DATA ? 332.47 : 331.83
};
}
return { marketPrice: 0 };
@ -117,8 +131,11 @@ export const CurrentRateServiceMock = {
values.push({
date,
dataSource: dataGatheringItem.dataSource,
marketPrice: mockGetValue(dataGatheringItem.symbol, date)
.marketPrice,
marketPrice: mockGetValue({
date,
dataSource: dataGatheringItem.dataSource,
symbol: dataGatheringItem.symbol
}).marketPrice,
symbol: dataGatheringItem.symbol
});
}
@ -132,8 +149,11 @@ export const CurrentRateServiceMock = {
values.push({
date,
dataSource: dataGatheringItem.dataSource,
marketPrice: mockGetValue(dataGatheringItem.symbol, date)
.marketPrice,
marketPrice: mockGetValue({
date,
dataSource: dataGatheringItem.dataSource,
symbol: dataGatheringItem.symbol
}).marketPrice,
symbol: dataGatheringItem.symbol
});
}

Loading…
Cancel
Save