Browse Source

Improve handling of excluded holdings

pull/7828/head
Thomas Kaul 3 days ago
parent
commit
cce9ab1c55
  1. 7
      apps/api/src/app/activities/activities.service.ts
  2. 8
      apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts
  3. 12
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  4. 48
      apps/api/src/app/portfolio/portfolio.service.ts
  5. 21
      libs/ui/src/lib/activities-table/activities-table.component.ts

7
apps/api/src/app/activities/activities.service.ts

@ -759,7 +759,8 @@ export class ActivitiesService {
filters, filters,
userCurrency, userCurrency,
userId, userId,
withCash = false withCash = false,
withExcludedAccountsAndActivities = false
}: { }: {
/** Optional filters to apply to the activities. */ /** Optional filters to apply to the activities. */
filters?: Filter[]; filters?: Filter[];
@ -769,13 +770,15 @@ export class ActivitiesService {
userId: string; userId: string;
/** Whether to include cash activities in the result. */ /** Whether to include cash activities in the result. */
withCash?: boolean; withCash?: boolean;
/** Whether to include activities that are excluded from analysis. */
withExcludedAccountsAndActivities?: boolean;
}) { }) {
const [activities, splits] = await Promise.all([ const [activities, splits] = await Promise.all([
this.getActivities({ this.getActivities({
filters, filters,
userCurrency, userCurrency,
userId, userId,
withExcludedAccountsAndActivities: false // TODO withExcludedAccountsAndActivities
}), }),
this.assetProfileSplitService.getSplitsByUserId({ userId }) this.assetProfileSplitService.getSplitsByUserId({ userId })
]); ]);

8
apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts

@ -34,7 +34,8 @@ export class PortfolioCalculatorFactory {
calculationType, calculationType,
currency, currency,
filters = [], filters = [],
userId userId,
usePortfolioSnapshotCache = true
}: { }: {
accountBalanceItems?: HistoricalDataItem[]; accountBalanceItems?: HistoricalDataItem[];
activities: Activity[]; activities: Activity[];
@ -42,6 +43,7 @@ export class PortfolioCalculatorFactory {
currency: string; currency: string;
filters?: Filter[]; filters?: Filter[];
userId: string; userId: string;
usePortfolioSnapshotCache?: boolean;
}): PortfolioCalculator { }): PortfolioCalculator {
switch (calculationType) { switch (calculationType) {
case PerformanceCalculationType.MWR: case PerformanceCalculationType.MWR:
@ -51,6 +53,7 @@ export class PortfolioCalculatorFactory {
currency, currency,
filters, filters,
userId, userId,
usePortfolioSnapshotCache,
configurationService: this.configurationService, configurationService: this.configurationService,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService, exchangeRateDataService: this.exchangeRateDataService,
@ -65,6 +68,7 @@ export class PortfolioCalculatorFactory {
currency, currency,
filters, filters,
userId, userId,
usePortfolioSnapshotCache,
configurationService: this.configurationService, configurationService: this.configurationService,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService, exchangeRateDataService: this.exchangeRateDataService,
@ -79,6 +83,7 @@ export class PortfolioCalculatorFactory {
currency, currency,
filters, filters,
userId, userId,
usePortfolioSnapshotCache,
configurationService: this.configurationService, configurationService: this.configurationService,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService, exchangeRateDataService: this.exchangeRateDataService,
@ -93,6 +98,7 @@ export class PortfolioCalculatorFactory {
currency, currency,
filters, filters,
userId, userId,
usePortfolioSnapshotCache,
configurationService: this.configurationService, configurationService: this.configurationService,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService, exchangeRateDataService: this.exchangeRateDataService,

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

@ -91,6 +91,7 @@ export abstract class PortfolioCalculator {
private startDate: Date; private startDate: Date;
private transactionPoints: TransactionPoint[]; private transactionPoints: TransactionPoint[];
private userId: string; private userId: string;
private usePortfolioSnapshotCache: boolean;
public constructor({ public constructor({
accountBalanceItems, accountBalanceItems,
@ -102,7 +103,8 @@ export abstract class PortfolioCalculator {
filters, filters,
portfolioSnapshotService, portfolioSnapshotService,
redisCacheService, redisCacheService,
userId userId,
usePortfolioSnapshotCache = true
}: { }: {
accountBalanceItems: HistoricalDataItem[]; accountBalanceItems: HistoricalDataItem[];
activities: Activity[]; activities: Activity[];
@ -114,6 +116,7 @@ export abstract class PortfolioCalculator {
portfolioSnapshotService: PortfolioSnapshotService; portfolioSnapshotService: PortfolioSnapshotService;
redisCacheService: RedisCacheService; redisCacheService: RedisCacheService;
userId: string; userId: string;
usePortfolioSnapshotCache?: boolean;
}) { }) {
this.accountBalanceItems = accountBalanceItems; this.accountBalanceItems = accountBalanceItems;
this.configurationService = configurationService; this.configurationService = configurationService;
@ -176,6 +179,7 @@ export abstract class PortfolioCalculator {
this.portfolioSnapshotService = portfolioSnapshotService; this.portfolioSnapshotService = portfolioSnapshotService;
this.redisCacheService = redisCacheService; this.redisCacheService = redisCacheService;
this.userId = userId; this.userId = userId;
this.usePortfolioSnapshotCache = usePortfolioSnapshotCache;
const { endDate, startDate } = getIntervalFromDateRange({ const { endDate, startDate } = getIntervalFromDateRange({
dateRange: 'max', dateRange: 'max',
@ -187,7 +191,11 @@ export abstract class PortfolioCalculator {
this.computeTransactionPoints(); this.computeTransactionPoints();
this.snapshotPromise = this.initialize(); this.snapshotPromise = this.usePortfolioSnapshotCache
? this.initialize()
: this.computeSnapshot().then((snapshot) => {
this.snapshot = snapshot;
});
// Mark the rejection as handled to prevent an unhandled promise rejection // Mark the rejection as handled to prevent an unhandled promise rejection
// in case the snapshot promise is never awaited. Consumers awaiting it // in case the snapshot promise is never awaited. Consumers awaiting it

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

@ -895,12 +895,36 @@ export class PortfolioService {
const user = await this.userService.user({ id: userId }); const user = await this.userService.user({ id: userId });
const userCurrency = this.getUserCurrency(user); const userCurrency = this.getUserCurrency(user);
const { activities } = const holdingFilters: Filter[] = [
{ id: dataSource, type: 'DATA_SOURCE' },
{ id: symbol, type: 'SYMBOL' }
];
const { activities: allActivitiesOfHolding } =
await this.activitiesService.getActivitiesForPortfolioCalculator({ await this.activitiesService.getActivitiesForPortfolioCalculator({
userCurrency, userCurrency,
userId userId,
filters: holdingFilters,
withExcludedAccountsAndActivities: true
}); });
if (allActivitiesOfHolding.length === 0) {
return undefined;
}
const hasExcludedActivities = allActivitiesOfHolding.some((activity) => {
return this.isExcludedFromAnalysis(activity);
});
const activities = hasExcludedActivities
? allActivitiesOfHolding
: (
await this.activitiesService.getActivitiesForPortfolioCalculator({
userCurrency,
userId
})
).activities;
if (activities.length === 0) { if (activities.length === 0) {
return undefined; return undefined;
} }
@ -927,7 +951,9 @@ export class PortfolioService {
activities, activities,
userId, userId,
calculationType: this.getUserPerformanceCalculationType(user), calculationType: this.getUserPerformanceCalculationType(user),
currency: userCurrency currency: userCurrency,
filters: hasExcludedActivities ? holdingFilters : undefined,
usePortfolioSnapshotCache: !hasExcludedActivities
}); });
const transactionPoints = portfolioCalculator.getTransactionPoints(); const transactionPoints = portfolioCalculator.getTransactionPoints();
@ -2033,12 +2059,7 @@ export class PortfolioService {
const nonExcludedActivities: Activity[] = []; const nonExcludedActivities: Activity[] = [];
for (const activity of activities) { for (const activity of activities) {
if ( if (this.isExcludedFromAnalysis(activity)) {
(activity.account && isAccountExcluded(activity.account)) ||
activity.tags?.some(({ id }) => {
return id === TAG_ID_EXCLUDE_FROM_ANALYSIS;
})
) {
excludedActivities.push(activity); excludedActivities.push(activity);
} else { } else {
nonExcludedActivities.push(activity); nonExcludedActivities.push(activity);
@ -2204,6 +2225,15 @@ export class PortfolioService {
}; };
} }
private isExcludedFromAnalysis(activity: Activity) {
return (
isAccountExcluded(activity.account) ||
activity.tags?.some(({ id }) => {
return id === TAG_ID_EXCLUDE_FROM_ANALYSIS;
}) === true
);
}
private getSumOfActivityType({ private getSumOfActivityType({
activities, activities,
activityType, activityType,

21
libs/ui/src/lib/activities-table/activities-table.component.ts

@ -1,13 +1,6 @@
import { import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
DEFAULT_PAGE_SIZE,
TAG_ID_EXCLUDE_FROM_ANALYSIS
} from '@ghostfolio/common/config';
import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { ConfirmationDialogType } from '@ghostfolio/common/enums';
import { import { getLocale, isDraftActivity } from '@ghostfolio/common/helper';
getLocale,
isAccountExcluded,
isDraftActivity
} from '@ghostfolio/common/helper';
import { import {
Activity, Activity,
AssetProfileIdentifier AssetProfileIdentifier
@ -286,7 +279,6 @@ export class GfActivitiesTableComponent implements AfterViewInit, OnInit {
public canClickActivity(activity: Activity) { public canClickActivity(activity: Activity) {
return ( return (
this.hasPermissionToOpenDetails && this.hasPermissionToOpenDetails &&
this.isExcludedFromAnalysis(activity) === false &&
isDraftActivity(activity) === false && isDraftActivity(activity) === false &&
['BUY', 'DIVIDEND', 'SELL'].includes(activity.type) ['BUY', 'DIVIDEND', 'SELL'].includes(activity.type)
); );
@ -306,15 +298,6 @@ export class GfActivitiesTableComponent implements AfterViewInit, OnInit {
); );
} }
public isExcludedFromAnalysis(activity: Activity) {
return (
isAccountExcluded(activity.account) ||
activity.tags?.some(({ id }) => {
return id === TAG_ID_EXCLUDE_FROM_ANALYSIS;
}) === true
);
}
public onChangePage(page: PageEvent) { public onChangePage(page: PageEvent) {
this.pageChanged.emit(page); this.pageChanged.emit(page);
} }

Loading…
Cancel
Save