Browse Source

Task/improve performance of portfolio snapshot calculation (#7656)

* Improve performance of portfolio snapshot calculation

* Update changelog
pull/7632/head
Thomas Kaul 19 hours ago
committed by GitHub
parent
commit
507e88cf9b
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 9
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  3. 43
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts

1
CHANGELOG.md

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Improved the performance of the portfolio snapshot calculation by indexing the activities
- Upgraded `angular` from version `21.2.19` to `22.1.2` - Upgraded `angular` from version `21.2.19` to `22.1.2`
- Upgraded `ngx-device-detector` from version `11.0.0` to `12.0.0` - Upgraded `ngx-device-detector` from version `11.0.0` to `12.0.0`
- Upgraded `ngx-markdown` from version `21.2.0` to `22.0.0` - Upgraded `ngx-markdown` from version `21.2.0` to `22.0.0`

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

@ -61,7 +61,7 @@ import {
startOfYear, startOfYear,
subDays subDays
} from 'date-fns'; } from 'date-fns';
import { isNumber, sortBy, sum, uniqBy } from 'lodash'; import { groupBy, isNumber, sortBy, sum, uniqBy } from 'lodash';
export abstract class PortfolioCalculator { export abstract class PortfolioCalculator {
protected static readonly ENABLE_LOGGING = false; protected static readonly ENABLE_LOGGING = false;
@ -72,6 +72,9 @@ export abstract class PortfolioCalculator {
protected accountBalanceItems: HistoricalDataItem[]; protected accountBalanceItems: HistoricalDataItem[];
protected activities: PortfolioOrder[]; protected activities: PortfolioOrder[];
protected activitiesBySymbol: {
[symbol: string]: PortfolioOrder[];
};
private configurationService: ConfigurationService; private configurationService: ConfigurationService;
private currency: string; private currency: string;
@ -162,6 +165,10 @@ export abstract class PortfolioCalculator {
return a.date?.localeCompare(b.date); return a.date?.localeCompare(b.date);
}); });
this.activitiesBySymbol = groupBy(this.activities, ({ assetProfile }) => {
return assetProfile.symbol;
});
this.portfolioSnapshotService = portfolioSnapshotService; this.portfolioSnapshotService = portfolioSnapshotService;
this.redisCacheService = redisCacheService; this.redisCacheService = redisCacheService;
this.userId = userId; this.userId = userId;

43
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts

@ -3,7 +3,7 @@ import { PortfolioCalculatorPosition } from '@ghostfolio/api/app/portfolio/inter
import { PortfolioOrderItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order-item.interface'; import { PortfolioOrderItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order-item.interface';
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper'; import { getFactor } from '@ghostfolio/api/helper/portfolio.helper';
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper';
import { import {
AssetProfileIdentifier, AssetProfileIdentifier,
SymbolMetrics SymbolMetrics
@ -21,7 +21,7 @@ import {
isBefore, isBefore,
isThisYear isThisYear
} from 'date-fns'; } from 'date-fns';
import { cloneDeep, sortBy } from 'lodash'; import { sortBy } from 'lodash';
export class RoaiPortfolioCalculator extends PortfolioCalculator { export class RoaiPortfolioCalculator extends PortfolioCalculator {
private chartDates: string[]; private chartDates: string[];
@ -192,12 +192,13 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
let valueAtStartDate: Big; let valueAtStartDate: Big;
let valueAtStartDateWithCurrencyEffect: Big; let valueAtStartDateWithCurrencyEffect: Big;
// Deep clone as the items are enriched below and the originals are shared // Copy the items as they are enriched below. A shallow copy is sufficient
let orders: PortfolioOrderItem[] = cloneDeep( // because only top-level properties are written.
this.activities.filter((activities) => { let orders: PortfolioOrderItem[] = (
return activities.assetProfile.symbol === symbol; this.activitiesBySymbol[symbol] ?? []
}) ).map((activity) => {
); return { ...activity };
});
const isCash = orders[0]?.assetProfile?.assetSubClass === 'CASH'; const isCash = orders[0]?.assetProfile?.assetSubClass === 'CASH';
@ -268,7 +269,8 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
} }
} }
const dateOfFirstTransaction = new Date(orders[0].date); const dateStringOfFirstActivity = orders[0].date;
const dateOfFirstActivity = parseDate(dateStringOfFirstActivity);
const endDateString = format(end, DATE_FORMAT); const endDateString = format(end, DATE_FORMAT);
const startDateString = format(start, DATE_FORMAT); const startDateString = format(start, DATE_FORMAT);
@ -276,7 +278,7 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
const unitPriceAtStartDate = marketSymbolMap[startDateString]?.[symbol]; const unitPriceAtStartDate = marketSymbolMap[startDateString]?.[symbol];
let unitPriceAtEndDate = marketSymbolMap[endDateString]?.[symbol]; let unitPriceAtEndDate = marketSymbolMap[endDateString]?.[symbol];
let latestActivity = orders.at(-1); const latestActivity = orders.at(-1);
if ( if (
dataSource === 'MANUAL' && dataSource === 'MANUAL' &&
@ -293,7 +295,7 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
if ( if (
!unitPriceAtEndDate || !unitPriceAtEndDate ||
(!unitPriceAtStartDate && isBefore(dateOfFirstTransaction, start)) (!unitPriceAtStartDate && isBefore(dateOfFirstActivity, start))
) { ) {
// A missing market price can only affect the quantity which is held. The // A missing market price can only affect the quantity which is held. The
// dividends, the interest and the liabilities do not hold any quantity // dividends, the interest and the liabilities do not hold any quantity
@ -367,7 +369,9 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
unitPrice: unitPriceAtEndDate unitPrice: unitPriceAtEndDate
}); });
let lastUnitPrice: Big; // Fall back to the unit price at the end date for the chart dates before
// the first known market price of the symbol
let lastUnitPrice = unitPriceAtEndDate;
const ordersByDate: { [date: string]: PortfolioOrderItem[] } = {}; const ordersByDate: { [date: string]: PortfolioOrderItem[] } = {};
@ -393,23 +397,24 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
marketSymbolMap[dateString]?.[symbol] ?? lastUnitPrice; marketSymbolMap[dateString]?.[symbol] ?? lastUnitPrice;
} }
} else { } else {
const unitPrice =
marketSymbolMap[dateString]?.[symbol] ?? lastUnitPrice;
if (dateString >= dateStringOfFirstActivity) {
orders.push({ orders.push({
assetProfile, assetProfile,
unitPrice,
date: dateString, date: dateString,
fee: new Big(0), fee: new Big(0),
feeInBaseCurrency: new Big(0), feeInBaseCurrency: new Big(0),
quantity: new Big(0), quantity: new Big(0),
type: 'BUY', type: 'BUY',
unitPrice: marketSymbolMap[dateString]?.[symbol] ?? lastUnitPrice, unitPriceFromMarketData: unitPrice
unitPriceFromMarketData:
marketSymbolMap[dateString]?.[symbol] ?? lastUnitPrice
}); });
} }
latestActivity = orders.at(-1); lastUnitPrice = unitPrice;
}
lastUnitPrice =
latestActivity.unitPriceFromMarketData ?? latestActivity.unitPrice;
} }
// Sort orders so that the start and end placeholder order are at the correct // Sort orders so that the start and end placeholder order are at the correct

Loading…
Cancel
Save