diff --git a/apps/api/src/app/portfolio/portfolio.service.spec.ts b/apps/api/src/app/portfolio/portfolio.service.spec.ts index c398d6a28..b503b18e9 100644 --- a/apps/api/src/app/portfolio/portfolio.service.spec.ts +++ b/apps/api/src/app/portfolio/portfolio.service.spec.ts @@ -606,5 +606,67 @@ describe('PortfolioService', () => { expect(accounts[account.id].quantity).toBeUndefined(); }); + + it('should only consider accounts of the current user if the activities are filtered by a single account', async () => { + const accountsSpy = jest.spyOn(accountService, 'accounts'); + + await getValueOfAccountsAndPlatforms({ + activities: [], + filters: [{ id: account.id, type: 'ACCOUNT' }], + portfolioItemsNow: {}, + userCurrency: 'USD', + userId: userDummyData.id + }); + + expect(accountsSpy).toHaveBeenCalledWith( + expect.objectContaining({ + where: { userId: userDummyData.id, id: account.id } + }) + ); + }); + + it('should exclude the cash balance if the activities are filtered by a single holding', async () => { + const { accounts, platforms } = await getValueOfAccountsAndPlatforms({ + activities: [ + { + account, + accountId: account.id, + assetProfile: { symbol: 'AAPL' }, + quantity: 1, + type: 'BUY' + } + ], + filters: [{ id: 'AAPL', type: 'SYMBOL' }], + portfolioItemsNow: { + AAPL: { marketPriceInBaseCurrency: 10 } + }, + userCurrency: 'USD', + userId: userDummyData.id + }); + + // 1 * 10 (activity), without the balance of 100 + expect(accounts[account.id].valueInBaseCurrency).toBe(10); + expect(platforms[account.platformId].valueInBaseCurrency).toBe(10); + }); + + it('should not accumulate rounding errors of the balances of accounts sharing a platform', async () => { + const platformId = randomUUID(); + + jest.spyOn(accountService, 'getAccounts').mockResolvedValue([ + { ...account, platformId, balance: 0.1, id: randomUUID() }, + { ...account, platformId, balance: 0.2, id: randomUUID() } + ] as unknown as AccountWithBalance[]); + + const { platforms } = await getValueOfAccountsAndPlatforms({ + activities: [], + filters: [], + portfolioItemsNow: {}, + userCurrency: 'USD', + userId: userDummyData.id + }); + + // 0.1 (balance) + 0.2 (balance) + expect(platforms[platformId].valueInBaseCurrency).toBe(0.3); + }); }); }); diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 1ec3dd234..25e79ceff 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -2214,7 +2214,7 @@ export class PortfolioService { } else if (filters.length === 1 && filters[0].type === 'ACCOUNT') { currentAccounts = await this.accountService.accounts({ include: { platform: true, tags: true }, - where: { id: filters[0].id } + where: { userId, id: filters[0].id } }); } else { const accountIds = Array.from( @@ -2242,39 +2242,43 @@ export class PortfolioService { // Iterate over the accounts plus a null entry to group activities without // an account into the unknown bucket for (const account of [...currentAccounts, null]) { + const currentAccountId = account?.id || UNKNOWN_KEY; + const currentPlatformId = account?.platformId || UNKNOWN_KEY; + const ordersByAccount = activities.filter(({ accountId }) => { return account ? accountId === account.id : !accountId; }); if (account) { - accounts[account.id] = { + // The cash balance is not part of a holding and would distort the value + // and thus the allocation per account and platform + const balanceInBaseCurrency = filterBySymbol + ? 0 + : this.exchangeRateDataService.toCurrency( + account.balance, + account.currency, + userCurrency + ); + + accounts[currentAccountId] = { balance: account.balance, currency: account.currency, name: account.name, - valueInBaseCurrency: this.exchangeRateDataService.toCurrency( - account.balance, - account.currency, - userCurrency - ) + valueInBaseCurrency: balanceInBaseCurrency }; - if (platforms[account.platformId || UNKNOWN_KEY]?.valueInBaseCurrency) { - platforms[account.platformId || UNKNOWN_KEY].valueInBaseCurrency += - this.exchangeRateDataService.toCurrency( - account.balance, - account.currency, - userCurrency - ); + if (platforms[currentPlatformId]) { + platforms[currentPlatformId].valueInBaseCurrency = new Big( + platforms[currentPlatformId].valueInBaseCurrency + ) + .plus(balanceInBaseCurrency) + .toNumber(); } else { - platforms[account.platformId || UNKNOWN_KEY] = { + platforms[currentPlatformId] = { balance: account.balance, currency: account.currency, name: account.platform?.name, - valueInBaseCurrency: this.exchangeRateDataService.toCurrency( - account.balance, - account.currency, - userCurrency - ) + valueInBaseCurrency: balanceInBaseCurrency }; } } @@ -2299,9 +2303,6 @@ export class PortfolioService { ); } - const currentAccountId = account?.id || UNKNOWN_KEY; - const currentPlatformId = account?.platformId || UNKNOWN_KEY; - // The quantity is only meaningful if the activities are filtered by a // single holding const quantityOfHolding = filterBySymbol