|
|
@ -14,7 +14,7 @@ import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-r |
|
|
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; |
|
|
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; |
|
|
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; |
|
|
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; |
|
|
import { parseDate } from '@ghostfolio/common/helper'; |
|
|
import { parseDate } from '@ghostfolio/common/helper'; |
|
|
import { HistoricalDataItem } from '@ghostfolio/common/interfaces'; |
|
|
import { TimelinePosition } from '@ghostfolio/common/models'; |
|
|
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; |
|
|
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; |
|
|
|
|
|
|
|
|
import { DataSource } from '@prisma/client'; |
|
|
import { DataSource } from '@prisma/client'; |
|
|
@ -191,7 +191,8 @@ describe('PortfolioCalculator', () => { |
|
|
const { activities } = await orderService.getOrdersForPortfolioCalculator( |
|
|
const { activities } = await orderService.getOrdersForPortfolioCalculator( |
|
|
{ |
|
|
{ |
|
|
userCurrency: 'CHF', |
|
|
userCurrency: 'CHF', |
|
|
userId: userDummyData.id |
|
|
userId: userDummyData.id, |
|
|
|
|
|
withCash: true |
|
|
} |
|
|
} |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
@ -217,97 +218,72 @@ describe('PortfolioCalculator', () => { |
|
|
|
|
|
|
|
|
const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); |
|
|
const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); |
|
|
|
|
|
|
|
|
const historicalData20231231 = portfolioSnapshot.historicalData.find( |
|
|
const position = portfolioSnapshot.positions.find(({ symbol }) => { |
|
|
({ date }) => { |
|
|
return symbol === 'USD'; |
|
|
return date === '2023-12-31'; |
|
|
|
|
|
} |
|
|
|
|
|
); |
|
|
|
|
|
const historicalData20240101 = portfolioSnapshot.historicalData.find( |
|
|
|
|
|
({ date }) => { |
|
|
|
|
|
return date === '2024-01-01'; |
|
|
|
|
|
} |
|
|
|
|
|
); |
|
|
|
|
|
const historicalData20241231 = portfolioSnapshot.historicalData.find( |
|
|
|
|
|
({ date }) => { |
|
|
|
|
|
return date === '2024-12-31'; |
|
|
|
|
|
} |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Investment value with currency effect: 1000 USD * 0.85 = 850 CHF |
|
|
|
|
|
* Total account balance: 1000 USD * 0.85 = 850 CHF (using the exchange rate on 2023-12-31) |
|
|
|
|
|
* Total investment: 1000 USD * 0.91 = 910 CHF |
|
|
|
|
|
* Value (current): 1000 USD * 0.91 = 910 CHF |
|
|
|
|
|
* Value with currency effect: 1000 USD * 0.85 = 850 CHF |
|
|
|
|
|
*/ |
|
|
|
|
|
expect(historicalData20231231).toMatchObject({ |
|
|
|
|
|
date: '2023-12-31', |
|
|
|
|
|
investmentValueWithCurrencyEffect: 850, |
|
|
|
|
|
netPerformance: 0, |
|
|
|
|
|
netPerformanceInPercentage: 0, |
|
|
|
|
|
netPerformanceInPercentageWithCurrencyEffect: 0, |
|
|
|
|
|
netPerformanceWithCurrencyEffect: 0, |
|
|
|
|
|
netWorth: 850, |
|
|
|
|
|
totalAccountBalance: 850, |
|
|
|
|
|
totalInvestment: 910, |
|
|
|
|
|
totalInvestmentValueWithCurrencyEffect: 850, |
|
|
|
|
|
value: 910, |
|
|
|
|
|
valueWithCurrencyEffect: 850 |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Net performance with currency effect: (1000 * 0.86) - (1000 * 0.85) = 10 CHF |
|
|
|
|
|
* Total account balance: 1000 USD * 0.85 = 850 CHF (using the exchange rate on 2023-12-31) |
|
|
|
|
|
* Total investment: 1000 USD * 0.91 = 910 CHF |
|
|
|
|
|
* Total investment value with currency effect: 1000 USD * 0.85 = 850 CHF |
|
|
|
|
|
* Value (current): 1000 USD * 0.91 = 910 CHF |
|
|
|
|
|
* Value with currency effect: 1000 USD * 0.86 = 860 CHF |
|
|
|
|
|
*/ |
|
|
|
|
|
expect(historicalData20240101).toMatchObject({ |
|
|
|
|
|
date: '2024-01-01', |
|
|
|
|
|
investmentValueWithCurrencyEffect: 0, |
|
|
|
|
|
netPerformance: 0, |
|
|
|
|
|
netPerformanceInPercentage: 0, |
|
|
|
|
|
netPerformanceInPercentageWithCurrencyEffect: 0.011764705882352941, |
|
|
|
|
|
netPerformanceWithCurrencyEffect: 10, |
|
|
|
|
|
netWorth: 860, |
|
|
|
|
|
totalAccountBalance: 850, |
|
|
|
|
|
totalInvestment: 910, |
|
|
|
|
|
totalInvestmentValueWithCurrencyEffect: 850, |
|
|
|
|
|
value: 910, |
|
|
|
|
|
valueWithCurrencyEffect: 860 |
|
|
|
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Investment value with currency effect: 1000 USD * 0.90 = 900 CHF |
|
|
* Investment: 2000 USD * 0.91 = 1820 CHF |
|
|
|
|
|
* Investment value with currency effect: (1000 USD * 0.85) + (1000 USD * 0.90) = 1750 CHF |
|
|
* Net performance: (1000 USD * 1.0) - (1000 USD * 1.0) = 0 CHF |
|
|
* Net performance: (1000 USD * 1.0) - (1000 USD * 1.0) = 0 CHF |
|
|
* Net performance with currency effect: (1000 USD * 0.9) - (1000 USD * 0.85) = 50 CHF |
|
|
|
|
|
* Total account balance: 2000 USD * 0.85 = 1700 CHF (using the exchange rate on 2024-12-31) |
|
|
* Total account balance: 2000 USD * 0.85 = 1700 CHF (using the exchange rate on 2024-12-31) |
|
|
* Total investment: 2000 USD * 0.91 = 1820 CHF |
|
|
* Value in base currency: 2000 USD * 0.91 = 1820 CHF |
|
|
* Total investment value with currency effect: (1000 USD * 0.85) + (1000 USD * 0.90) = 1750 CHF |
|
|
|
|
|
* Value (current): 2000 USD * 0.91 = 1820 CHF |
|
|
|
|
|
* Value with currency effect: 2000 USD * 0.9 = 1800 CHF |
|
|
|
|
|
*/ |
|
|
*/ |
|
|
expect(historicalData20241231).toMatchObject<HistoricalDataItem>({ |
|
|
expect(position).toMatchObject<TimelinePosition>({ |
|
|
date: '2024-12-31', |
|
|
averagePrice: new Big(1), |
|
|
investmentValueWithCurrencyEffect: 900, |
|
|
currency: 'USD', |
|
|
netPerformance: 0, |
|
|
dataSource: DataSource.YAHOO, |
|
|
netPerformanceInPercentage: 0, |
|
|
dividend: new Big(0), |
|
|
netPerformanceInPercentageWithCurrencyEffect: 0.058823529411764705, |
|
|
dividendInBaseCurrency: new Big(0), |
|
|
netPerformanceWithCurrencyEffect: 50, |
|
|
fee: new Big(0), |
|
|
netWorth: 1800, |
|
|
feeInBaseCurrency: new Big(0), |
|
|
totalAccountBalance: 1800, |
|
|
firstBuyDate: '2023-12-31', |
|
|
totalInvestment: 1820, |
|
|
grossPerformance: new Big(0), |
|
|
totalInvestmentValueWithCurrencyEffect: 1750, |
|
|
grossPerformancePercentage: new Big(0), |
|
|
value: 1820, |
|
|
grossPerformancePercentageWithCurrencyEffect: new Big( |
|
|
valueWithCurrencyEffect: 1800 |
|
|
'0.08211603004634809014' |
|
|
|
|
|
), |
|
|
|
|
|
grossPerformanceWithCurrencyEffect: new Big(70), |
|
|
|
|
|
includeInTotalAssetValue: false, |
|
|
|
|
|
investment: new Big(1820), |
|
|
|
|
|
investmentWithCurrencyEffect: new Big(1750), |
|
|
|
|
|
marketPrice: null, |
|
|
|
|
|
marketPriceInBaseCurrency: 0.91, |
|
|
|
|
|
netPerformance: new Big(0), |
|
|
|
|
|
netPerformancePercentage: new Big(0), |
|
|
|
|
|
netPerformancePercentageWithCurrencyEffectMap: { |
|
|
|
|
|
'1d': new Big('0.01111111111111111111'), |
|
|
|
|
|
'1y': new Big('0.06937181021989792704'), |
|
|
|
|
|
'5y': new Big('0.0818817546090273363'), |
|
|
|
|
|
max: new Big('0.0818817546090273363'), |
|
|
|
|
|
mtd: new Big('0.01111111111111111111'), |
|
|
|
|
|
wtd: new Big('-0.05517241379310344828'), |
|
|
|
|
|
ytd: new Big('0.01111111111111111111') |
|
|
|
|
|
}, |
|
|
|
|
|
netPerformanceWithCurrencyEffectMap: { |
|
|
|
|
|
'1d': new Big(20), |
|
|
|
|
|
'1y': new Big(60), |
|
|
|
|
|
'5y': new Big(70), |
|
|
|
|
|
max: new Big(70), |
|
|
|
|
|
mtd: new Big(20), |
|
|
|
|
|
wtd: new Big(-80), |
|
|
|
|
|
ytd: new Big(20) |
|
|
|
|
|
}, |
|
|
|
|
|
quantity: new Big(2000), |
|
|
|
|
|
symbol: 'USD', |
|
|
|
|
|
timeWeightedInvestment: new Big('912.47956403269754768392'), |
|
|
|
|
|
timeWeightedInvestmentWithCurrencyEffect: new Big( |
|
|
|
|
|
'852.45231607629427792916' |
|
|
|
|
|
), |
|
|
|
|
|
transactionCount: 2, |
|
|
|
|
|
valueInBaseCurrency: new Big(1820) |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
expect(portfolioSnapshot).toMatchObject({ |
|
|
expect(portfolioSnapshot).toMatchObject({ |
|
|
hasErrors: false, |
|
|
hasErrors: false, |
|
|
totalFeesWithCurrencyEffect: new Big('0'), |
|
|
totalFeesWithCurrencyEffect: new Big(0), |
|
|
totalInterestWithCurrencyEffect: new Big('0'), |
|
|
totalInterestWithCurrencyEffect: new Big(0), |
|
|
totalLiabilitiesWithCurrencyEffect: new Big('0') |
|
|
totalLiabilitiesWithCurrencyEffect: new Big(0) |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
|