Browse Source

fix: ignore future-dated account balances in portfolio calculation (fixes #6185)

- Filter balances to date <= today in getAccountBalanceItems() so the
  balance time series is not affected by future entries
- Filter balances to date <= endOfToday() in order service when building
  synthetic cash activities so future balances do not create SELLs that
  zero the position
- Extend portfolio-calculator-cash.spec.ts mock with a future-dated
  balance (2050-12-31) to assert it is ignored
pull/6318/head
irfanfaraaz 2 weeks ago
parent
commit
211f4346ce
  1. 12
      apps/api/src/app/account-balance/account-balance.service.ts
  2. 8
      apps/api/src/app/order/order.service.ts
  3. 8
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts

12
apps/api/src/app/account-balance/account-balance.service.ts

@ -14,7 +14,7 @@ import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { AccountBalance, Prisma } from '@prisma/client';
import { Big } from 'big.js';
import { format, parseISO } from 'date-fns';
import { endOfDay, format, parseISO } from 'date-fns';
@Injectable()
export class AccountBalanceService {
@ -109,11 +109,19 @@ export class AccountBalanceService {
userId,
withExcludedAccounts: false // TODO
});
// Exclude future-dated balances so they do not affect portfolio calculation (fixes #6185)
const asOfDate = endOfDay(new Date());
const asOfTime = asOfDate.getTime();
const balancesUpToToday = balances.filter(({ date }) => {
return date.getTime() <= asOfTime;
});
const accumulatedBalancesByDate: { [date: string]: HistoricalDataItem } =
{};
const lastBalancesByAccount: { [accountId: string]: Big } = {};
for (const { accountId, date, valueInBaseCurrency } of balances) {
for (const { accountId, date, valueInBaseCurrency } of balancesUpToToday) {
const formattedDate = format(date, DATE_FORMAT);
lastBalancesByAccount[accountId] = new Big(valueInBaseCurrency);

8
apps/api/src/app/order/order.service.ts

@ -371,10 +371,16 @@ export class OrderService {
filters: [{ id: account.id, type: 'ACCOUNT' }]
});
// Exclude future-dated balances so they do not affect portfolio calculation (fixes #6185)
const asOfDate = endOfToday();
const balancesUpToToday = balances.filter(({ date }) => {
return date <= asOfDate;
});
let currentBalance = 0;
let currentBalanceInBaseCurrency = 0;
for (const balanceItem of balances) {
for (const balanceItem of balancesUpToToday) {
const syntheticActivityTemplate: Activity = {
userId,
accountId: account.id,

8
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts

@ -157,6 +157,14 @@ describe('PortfolioCalculator', () => {
date: parseDate('2024-12-31'),
value: 2000,
valueInBaseCurrency: 1800
},
{
// Future-dated balance: should be ignored (issue #6185)
accountId,
date: parseDate('2050-12-31'),
id: randomUUID(),
value: 0,
valueInBaseCurrency: 0
}
]
});

Loading…
Cancel
Save