Browse Source

Feature/improve performance of portfolio snapshot computation - 2

Continuing my perf analysis of the portfolio snapshot computation, this was another big time sink on my machine, specially when generating each day for the `'5y'` and `'max'` date ranges.

For reference, before this diff, the `dateRange` loop was taking on average ~100ms per symbol for me (Raspberry Pi 4, 478 symbols in the DB). After this diff, it takes ~15ms per symbol, shaving off 40s of computation time.

Test Plan:
Check that result of portfolio snapshot computation is the same before and after this diff:
1. Flush portfolio snapshot cache:
```
$ docker exec -it ghostfolio-redis-1 redis-cli --pass $REDIS_PASSWORD del "portfolio-snapshot-f9e4c63e-4b8e-46fc-b6ed-75ca0205f12b"
```
2. Open Accounts to trigger snapshot calculation
3. Dump portfolio snapshot to a file:
```
$ docker exec -it ghostfolio-redis-1 redis-cli --pass $REDIS_PASSWORD --no-auth-warning get "portfolio-snapshot-f9e4c63e-4b8e-46fc-b6ed-75ca0205f12b" | python -c "import json, sys; json.dump(json.loads(json.loads(json.load(sys.stdin))), sys.stdout, indent=2, sort_keys=True)" > snapshot-before.json
```
4. Apply this patch
5. Repeat steps 1-3
6. Compare results, check everything matches except for expiration time:
```
$ diff snapshot-before.json snapshot-after.json
2c2
<   "expiration": 1727449402720,
---
>   "expiration": 1727449639794,
```
pull/3829/head
ceroma 11 months ago
parent
commit
c9cd59f443
  1. 42
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts

42
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts

@ -16,13 +16,14 @@ import {
addDays,
addMilliseconds,
differenceInDays,
eachDayOfInterval,
format,
isBefore
} from 'date-fns';
import { cloneDeep, first, last, sortBy } from 'lodash';
export class TWRPortfolioCalculator extends PortfolioCalculator {
private chartDatesDescending: string[];
protected calculateOverallPerformance(
positions: TimelinePosition[]
): PortfolioSnapshot {
@ -820,31 +821,35 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
startDate = start;
}
const startDateString = format(startDate, DATE_FORMAT);
const endDateString = format(endDate, DATE_FORMAT);
const currentValuesAtDateRangeStartWithCurrencyEffect =
currentValuesWithCurrencyEffect[format(startDate, DATE_FORMAT)] ??
new Big(0);
currentValuesWithCurrencyEffect[startDateString] ?? new Big(0);
const investmentValuesAccumulatedAtStartDateWithCurrencyEffect =
investmentValuesAccumulatedWithCurrencyEffect[
format(startDate, DATE_FORMAT)
] ?? new Big(0);
investmentValuesAccumulatedWithCurrencyEffect[startDateString] ??
new Big(0);
const grossPerformanceAtDateRangeStartWithCurrencyEffect =
currentValuesAtDateRangeStartWithCurrencyEffect.minus(
investmentValuesAccumulatedAtStartDateWithCurrencyEffect
);
const dates = eachDayOfInterval({
end: endDate,
start: startDate
}).map((date) => {
return format(date, DATE_FORMAT);
});
let average = new Big(0);
let dayCount = 0;
for (const date of dates) {
if (!this.chartDatesDescending) {
this.chartDatesDescending = Object.keys(chartDateMap).sort().reverse();
}
for (const date of this.chartDatesDescending) {
if (date > endDateString) {
continue;
} else if (date < startDateString) {
break;
}
if (
investmentValuesAccumulatedWithCurrencyEffect[date] instanceof Big &&
investmentValuesAccumulatedWithCurrencyEffect[date].gt(0)
@ -864,17 +869,14 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
}
netPerformanceWithCurrencyEffectMap[dateRange] =
netPerformanceValuesWithCurrencyEffect[
format(endDate, DATE_FORMAT)
]?.minus(
netPerformanceValuesWithCurrencyEffect[endDateString]?.minus(
// If the date range is 'max', take 0 as a start value. Otherwise,
// the value of the end of the day of the start date is taken which
// differs from the buying price.
dateRange === 'max'
? new Big(0)
: (netPerformanceValuesWithCurrencyEffect[
format(startDate, DATE_FORMAT)
] ?? new Big(0))
: (netPerformanceValuesWithCurrencyEffect[startDateString] ??
new Big(0))
) ?? new Big(0);
netPerformancePercentageWithCurrencyEffectMap[dateRange] = average.gt(0)

Loading…
Cancel
Save